Refactoring code

This commit is contained in:
2026-01-16 11:33:11 -05:00
parent 9dfb52ac8d
commit 4fb52596e3
46 changed files with 1448 additions and 1025 deletions

View File

@@ -31,10 +31,7 @@ local M = {}
---@field on_complete? fun(result: string|nil, error: string|nil) Called when done
---@field on_status? fun(status: string) Status updates
--- Generate unique tool call ID
local function generate_tool_call_id()
return "call_" .. string.format("%x", os.time()) .. "_" .. string.format("%x", math.random(0, 0xFFFF))
end
local utils = require("codetyper.utils")
--- Load agent definition
---@param name string Agent name
@@ -72,59 +69,7 @@ local function load_agent(name)
end
-- Built-in agents
local builtin_agents = {
coder = {
name = "coder",
description = "Full-featured coding agent with file modification capabilities",
system_prompt = [[You are an expert software engineer. You have access to tools to read, write, and modify files.
## Your Capabilities
- Read files to understand the codebase
- Search for patterns with grep and glob
- Create new files with write tool
- Edit existing files with precise replacements
- Execute shell commands for builds and tests
## Guidelines
1. Always read relevant files before making changes
2. Make minimal, focused changes
3. Follow existing code style and patterns
4. Create tests when adding new functionality
5. Verify changes work by running tests or builds
## Important Rules
- NEVER guess file contents - always read first
- Make precise edits using exact string matching
- Explain your reasoning before making changes
- If unsure, ask for clarification]],
tools = { "view", "edit", "write", "grep", "glob", "bash" },
},
planner = {
name = "planner",
description = "Planning agent - read-only, helps design implementations",
system_prompt = [[You are a software architect. Analyze codebases and create implementation plans.
You can read files and search the codebase, but cannot modify files.
Your role is to:
1. Understand the existing architecture
2. Identify relevant files and patterns
3. Create step-by-step implementation plans
4. Suggest which files to modify and how
Be thorough in your analysis before making recommendations.]],
tools = { "view", "grep", "glob" },
},
explorer = {
name = "explorer",
description = "Exploration agent - quickly find information in codebase",
system_prompt = [[You are a codebase exploration assistant. Find information quickly and report back.
Your goal is to efficiently search and summarize findings.
Use glob to find files, grep to search content, and view to read specific files.
Be concise and focused in your responses.]],
tools = { "view", "grep", "glob" },
},
}
local builtin_agents = require("codetyper.prompts.agents.personas").builtin
return builtin_agents[name]
end
@@ -365,7 +310,7 @@ local function parse_tool_calls(response, provider)
end
table.insert(tool_calls, {
id = block.id or generate_tool_call_id(),
id = block.id or utils.generate_id("call"),
type = "function",
["function"] = {
name = block.name,
@@ -449,7 +394,7 @@ local function call_llm(messages, tools, system_prompt, provider, model, callbac
elseif block.type == "tool_use" then
table.insert(result.content, {
type = "tool_use",
id = block.id or generate_tool_call_id(),
id = block.id or utils.generate_id("call"),
name = block.name,
input = block.input,
})
@@ -490,7 +435,7 @@ local function call_llm(messages, tools, system_prompt, provider, model, callbac
elseif block.type == "tool_use" then
table.insert(result.content, {
type = "tool_use",
id = block.id or generate_tool_call_id(),
id = block.id or utils.generate_id("call"),
name = block.name,
input = block.input,
})
@@ -526,21 +471,20 @@ local function call_llm(messages, tools, system_prompt, provider, model, callbac
local client = require("codetyper.llm." .. provider)
-- Build prompt from messages
local prompts = require("codetyper.prompts.agent")
local prompt_parts = {}
for _, msg in ipairs(messages) do
if msg.role == "user" then
local content = type(msg.content) == "string" and msg.content or vim.json.encode(msg.content)
table.insert(prompt_parts, "User: " .. content)
table.insert(prompt_parts, prompts.text_user_prefix .. content)
elseif msg.role == "assistant" then
local content = type(msg.content) == "string" and msg.content or vim.json.encode(msg.content)
table.insert(prompt_parts, "Assistant: " .. content)
table.insert(prompt_parts, prompts.text_assistant_prefix .. content)
end
end
-- Add tool descriptions to prompt for text-based providers
local tool_desc = "\n\n## Available Tools\n"
tool_desc = tool_desc .. "Call tools by outputting JSON in this format:\n"
tool_desc = tool_desc .. '```json\n{"tool": "tool_name", "arguments": {...}}\n```\n\n'
local tool_desc = require("codetyper.prompts.agent").tool_instructions_text
for _, tool in ipairs(tools) do
local name = tool.name or (tool["function"] and tool["function"].name)
local desc = tool.description or (tool["function"] and tool["function"].description)
@@ -573,7 +517,7 @@ local function call_llm(messages, tools, system_prompt, provider, model, callbac
if ok and parsed.tool then
table.insert(result.content, {
type = "tool_use",
id = generate_tool_call_id(),
id = utils.generate_id("call"),
name = parsed.tool,
input = parsed.arguments or {},
})
@@ -617,11 +561,7 @@ function M.run(opts)
-- Add initial file context if provided
if opts.files and #opts.files > 0 then
local file_context = "# Initial Files\n"
for _, file_path in ipairs(opts.files) do
local content = table.concat(vim.fn.readfile(file_path) or {}, "\n")
file_context = file_context .. string.format("\n## %s\n```\n%s\n```\n", file_path, content)
end
local file_context = require("codetyper.prompts.agent").format_file_context(opts.files)
table.insert(history, { role = "user", content = file_context })
table.insert(history, { role = "assistant", content = "I've reviewed the provided files. What would you like me to do?" })
end
@@ -736,27 +676,7 @@ function M.init_agents_dir()
vim.fn.mkdir(agents_dir, "p")
-- Create example agent
local example_agent = [[---
description: Example custom agent
tools: view,grep,glob,edit,write
model:
---
# Custom Agent
You are a custom coding agent. Describe your specialized behavior here.
## Your Role
- Define what this agent specializes in
- List specific capabilities
## Guidelines
- Add agent-specific rules
- Define coding standards to follow
## Examples
Provide examples of how to handle common tasks.
]]
local example_agent = require("codetyper.prompts.agents.templates").agent
local example_path = agents_dir .. "/example.md"
if vim.fn.filereadable(example_path) ~= 1 then
@@ -772,30 +692,7 @@ function M.init_rules_dir()
vim.fn.mkdir(rules_dir, "p")
-- Create example rule
local example_rule = [[# Code Style
Follow these coding standards:
## General
- Use consistent indentation (tabs or spaces based on project)
- Keep lines under 100 characters
- Add comments for complex logic
## Naming Conventions
- Use descriptive variable names
- Functions should be verbs (e.g., getUserData, calculateTotal)
- Constants in UPPER_SNAKE_CASE
## Testing
- Write tests for new functionality
- Aim for >80% code coverage
- Test edge cases
## Documentation
- Document public APIs
- Include usage examples
- Keep docs up to date with code
]]
local example_rule = require("codetyper.prompts.agents.templates").rule
local example_path = rules_dir .. "/code-style.md"
if vim.fn.filereadable(example_path) ~= 1 then
@@ -817,7 +714,10 @@ function M.list_agents()
local agents = {}
-- Built-in agents
local builtins = { "coder", "planner", "explorer" }
local personas = require("codetyper.prompts.agents.personas").builtin
local builtins = vim.tbl_keys(personas)
table.sort(builtins)
for _, name in ipairs(builtins) do
local agent = load_agent(name)
if agent then

View File

@@ -6,41 +6,14 @@
local M = {}
local params = require("codetyper.params.agent.confidence")
--- Heuristic weights (must sum to 1.0)
M.weights = {
length = 0.15, -- Response length relative to prompt
uncertainty = 0.30, -- Uncertainty phrases
syntax = 0.25, -- Syntax completeness
repetition = 0.15, -- Duplicate lines
truncation = 0.15, -- Incomplete ending
}
M.weights = params.weights
--- Uncertainty phrases that indicate low confidence
local uncertainty_phrases = {
-- English
"i'm not sure",
"i am not sure",
"maybe",
"perhaps",
"might work",
"could work",
"not certain",
"uncertain",
"i think",
"possibly",
"TODO",
"FIXME",
"XXX",
"placeholder",
"implement this",
"fill in",
"your code here",
"...", -- Ellipsis as placeholder
"# TODO",
"// TODO",
"-- TODO",
"/* TODO",
}
local uncertainty_phrases = params.uncertainty_phrases
--- Score based on response length relative to prompt
---@param response string
@@ -94,32 +67,6 @@ local function score_uncertainty(response)
end
end
--- Check bracket balance for common languages
---@param response string
---@return boolean balanced
local function check_brackets(response)
local pairs = {
["{"] = "}",
["["] = "]",
["("] = ")",
}
local stack = {}
for char in response:gmatch(".") do
if pairs[char] then
table.insert(stack, pairs[char])
elseif char == "}" or char == "]" or char == ")" then
if #stack == 0 or stack[#stack] ~= char then
return false
end
table.remove(stack)
end
end
return #stack == 0
end
--- Score based on syntax completeness
---@param response string
---@return number 0.0-1.0
@@ -127,7 +74,7 @@ local function score_syntax(response)
local score = 1.0
-- Check bracket balance
if not check_brackets(response) then
if not require("codetyper.utils").check_brackets(response) then
score = score - 0.4
end

View File

@@ -15,22 +15,15 @@
local M = {}
local params = require("codetyper.params.agent.conflict")
--- Lazy load linter module
local function get_linter()
return require("codetyper.agent.linter")
end
--- Configuration
local config = {
-- Run linter check after accepting AI suggestions
lint_after_accept = true,
-- Auto-fix lint errors without prompting
auto_fix_lint_errors = true,
-- Auto-show menu after injecting conflict
auto_show_menu = true,
-- Auto-show menu for next conflict after resolving one
auto_show_next_menu = true,
}
local config = vim.deepcopy(params.config)
--- Namespace for conflict highlighting
local NAMESPACE = vim.api.nvim_create_namespace("codetyper_conflict")
@@ -39,24 +32,12 @@ local NAMESPACE = vim.api.nvim_create_namespace("codetyper_conflict")
local HINT_NAMESPACE = vim.api.nvim_create_namespace("codetyper_conflict_hints")
--- Highlight groups
local HL_GROUPS = {
current = "CoderConflictCurrent",
current_label = "CoderConflictCurrentLabel",
incoming = "CoderConflictIncoming",
incoming_label = "CoderConflictIncomingLabel",
separator = "CoderConflictSeparator",
hint = "CoderConflictHint",
}
local HL_GROUPS = params.hl_groups
--- Conflict markers
local MARKERS = {
current_start = "<<<<<<< CURRENT",
separator = "=======",
incoming_end = ">>>>>>> INCOMING",
}
local MARKERS = params.markers
--- Track buffers with active conflicts
---@type table<number, table>
local conflict_buffers = {}
--- Run linter validation after accepting code changes

View File

@@ -6,6 +6,7 @@
local M = {}
local utils = require("codetyper.utils")
local params = require("codetyper.params.agent.context")
--- Get project structure as a tree string
---@param max_depth? number Maximum depth to traverse (default: 3)
@@ -20,21 +21,7 @@ function M.get_project_structure(max_depth, max_files)
local file_count = 0
-- Common ignore patterns
local ignore_patterns = {
"^%.", -- Hidden files/dirs
"node_modules",
"%.git$",
"__pycache__",
"%.pyc$",
"target", -- Rust
"build",
"dist",
"%.o$",
"%.a$",
"%.so$",
"%.min%.",
"%.map$",
}
local ignore_patterns = params.ignore_patterns
local function should_ignore(name)
for _, pattern in ipairs(ignore_patterns) do
@@ -127,19 +114,7 @@ function M.get_key_files()
["plugin.lua"] = "Neovim plugin config",
}
for filename, desc in pairs(important_files) do
-- Check in root
local path = root .. "/" .. filename
if vim.fn.filereadable(path) == 1 then
key_files[filename] = { path = path, description = desc }
end
-- Check in lua/ for Neovim plugins
local lua_path = root .. "/lua/" .. filename
if vim.fn.filereadable(lua_path) == 1 then
key_files["lua/" .. filename] = { path = lua_path, description = desc }
end
end
for filename, desc in paparams.important_filesnd
return key_files
end
@@ -161,16 +136,7 @@ function M.detect_project_type()
}
-- Check for Neovim plugin specifically
if vim.fn.isdirectory(root .. "/lua") == 1 then
local plugin_files = vim.fn.glob(root .. "/plugin/*.lua", false, true)
if #plugin_files > 0 or vim.fn.filereadable(root .. "/init.lua") == 1 then
return { type = "neovim-plugin", language = "lua", framework = "neovim" }
end
end
for file, info in pairs(indicators) do
if vim.fn.filereadable(root .. "/" .. file) == 1 then
return info
if vim.fn.isdirectoparams.indicators return info
end
end

View File

@@ -122,7 +122,8 @@ local function attach_requested_files()
end
local files = parse_requested_files(state.llm_response)
if #files == 0 then
vim.api.nvim_buf_set_lines(state.buf, vim.api.nvim_buf_line_count(state.buf), -1, false, { "", "-- No files detected in LLM response --" })
local ui_prompts = require("codetyper.prompts.agent.modal").ui
vim.api.nvim_buf_set_lines(state.buf, vim.api.nvim_buf_line_count(state.buf), -1, false, ui_prompts.files_header)
return
end
@@ -190,9 +191,11 @@ function M.open(original_event, llm_response, callback, suggested_commands)
vim.wo[state.win].wrap = true
vim.wo[state.win].cursorline = true
local ui_prompts = require("codetyper.prompts.agent.modal").ui
-- Add header showing what the LLM said
local header_lines = {
"-- LLM Response: --",
ui_prompts.llm_response_header,
}
-- Truncate LLM response for display
@@ -207,16 +210,16 @@ function M.open(original_event, llm_response, callback, suggested_commands)
-- If suggested commands were provided, show them in the header
if suggested_commands and #suggested_commands > 0 then
table.insert(header_lines, "")
table.insert(header_lines, "-- Suggested commands: --")
table.insert(header_lines, ui_prompts.suggested_commands_header)
for i, s in ipairs(suggested_commands) do
local label = s.label or s.cmd
table.insert(header_lines, string.format("[%d] %s: %s", i, label, s.cmd))
end
table.insert(header_lines, "-- Press <leader><n> to run a command, or <leader>r to run all --")
table.insert(header_lines, ui_prompts.commands_hint)
end
table.insert(header_lines, "")
table.insert(header_lines, "-- Enter additional context below (Ctrl-Enter to submit, Esc to cancel) --")
table.insert(header_lines, ui_prompts.input_header)
table.insert(header_lines, "")
vim.api.nvim_buf_set_lines(state.buf, 0, -1, false, header_lines)
@@ -322,8 +325,9 @@ local function run_project_inspect()
{ label = "Show repo files (git ls-files)", cmd = "git ls-files" },
}
local ui_prompts = require("codetyper.prompts.agent.modal").ui
local insert_pos = vim.api.nvim_buf_line_count(state.buf)
vim.api.nvim_buf_set_lines(state.buf, insert_pos, insert_pos, false, { "", "-- Project inspection results --" })
vim.api.nvim_buf_set_lines(state.buf, insert_pos, insert_pos, false, ui_prompts.project_inspect_header)
for _, c in ipairs(cmds) do
local ok, out = pcall(vim.fn.systemlist, c.cmd)

View File

@@ -157,17 +157,19 @@ function M.show_diff(diff_data, callback)
end
-- Show help message
vim.api.nvim_echo({
{ "Diff: ", "Normal" },
{ diff_data.path, "Directory" },
{ " | ", "Normal" },
{ "y/<CR>", "Keyword" },
{ " approve ", "Normal" },
{ "n/q/<Esc>", "Keyword" },
{ " reject ", "Normal" },
{ "<Tab>", "Keyword" },
{ " switch panes", "Normal" },
}, false, {})
local help_msg = require("codetyper.prompts.agent.diff").diff_help
-- Iterate to replace {path} variable
local final_help = {}
for _, item in ipairs(help_msg) do
if item[1] == "{path}" then
table.insert(final_help, { diff_data.path, item[2] })
else
table.insert(final_help, item)
end
end
vim.api.nvim_echo(final_help, false, {})
end
---@alias BashApprovalResult {approved: boolean, permission_level: string|nil}
@@ -188,31 +190,31 @@ function M.show_bash_approval(command, callback)
end
-- Create approval dialog with options
local approval_prompts = require("codetyper.prompts.agent.diff").bash_approval
local lines = {
"",
" BASH COMMAND APPROVAL",
" " .. string.rep("", 56),
approval_prompts.title,
approval_prompts.divider,
"",
" Command:",
approval_prompts.command_label,
" $ " .. command,
"",
}
-- Add warning for dangerous commands
if not perm_result.allowed and perm_result.reason ~= "Requires approval" then
table.insert(lines, " ⚠️ WARNING: " .. perm_result.reason)
table.insert(lines, approval_prompts.warning_prefix .. perm_result.reason)
table.insert(lines, "")
end
table.insert(lines, " " .. string.rep("", 56))
table.insert(lines, approval_prompts.divider)
table.insert(lines, "")
table.insert(lines, " [y] Allow once - Execute this command")
table.insert(lines, " [s] Allow this session - Auto-allow until restart")
table.insert(lines, " [a] Add to allow list - Always allow this command")
table.insert(lines, " [n] Reject - Cancel execution")
for _, opt in ipairs(approval_prompts.options) do
table.insert(lines, opt)
end
table.insert(lines, "")
table.insert(lines, " " .. string.rep("", 56))
table.insert(lines, " Press key to choose | [q] or [Esc] to cancel")
table.insert(lines, approval_prompts.divider)
table.insert(lines, approval_prompts.cancel_hint)
table.insert(lines, "")
local width = math.max(65, #command + 15)

View File

@@ -6,6 +6,8 @@
local M = {}
local utils = require("codetyper.utils")
local prompts = require("codetyper.prompts.agent.diff")
---@class DiffEntry
---@field path string File path
@@ -124,9 +126,10 @@ local function update_diff_view()
end
local entry = state.entries[state.current_index]
local ui_prompts = prompts.review
if not entry then
vim.bo[state.diff_buf].modifiable = true
vim.api.nvim_buf_set_lines(state.diff_buf, 0, -1, false, { "No changes to review" })
vim.api.nvim_buf_set_lines(state.diff_buf, 0, -1, false, { ui_prompts.messages.no_changes_short })
vim.bo[state.diff_buf].modifiable = false
return
end
@@ -134,15 +137,17 @@ local function update_diff_view()
local lines = {}
-- Header
local status_icon = entry.applied and "" or (entry.approved and "" or "")
local status_icon = entry.applied and " " or (entry.approved and " " or " ")
local op_icon = entry.operation == "create" and "+" or (entry.operation == "delete" and "-" or "~")
local current_status = entry.applied and ui_prompts.status.applied
or (entry.approved and ui_prompts.status.approved or ui_prompts.status.pending)
table.insert(lines, string.format("╭─ %s %s %s ─────────────────────────────────────",
table.insert(lines, string.format(ui_prompts.diff_header.top,
status_icon, op_icon, vim.fn.fnamemodify(entry.path, ":t")))
table.insert(lines, "" .. entry.path)
table.insert(lines, "│ Operation: " .. entry.operation)
table.insert(lines, "│ Status: " .. (entry.applied and "Applied" or (entry.approved and "Approved" or "Pending")))
table.insert(lines, "╰────────────────────────────────────────────────────")
table.insert(lines, string.format(ui_prompts.diff_header.path, entry.path))
table.insert(lines, string.format(ui_prompts.diff_header.op, entry.operation))
table.insert(lines, string.format(ui_prompts.diff_header.status, current_status))
table.insert(lines, ui_prompts.diff_header.bottom)
table.insert(lines, "")
-- Diff content
@@ -163,17 +168,14 @@ local function update_file_list()
return
end
local lines = {
"╭─ Changes (" .. #state.entries .. ") ──────────╮",
"│ │",
"│ j/k: navigate │",
"│ Enter: view diff │",
"│ a: approve r: reject │",
"│ A: approve all │",
"│ q: close │",
"╰──────────────────────────────╯",
"",
}
local ui_prompts = prompts.review
local lines = {}
table.insert(lines, string.format(ui_prompts.list_menu.top, #state.entries))
for _, item in ipairs(ui_prompts.list_menu.items) do
table.insert(lines, item)
end
table.insert(lines, ui_prompts.list_menu.bottom)
table.insert(lines, "")
for i, entry in ipairs(state.entries) do
local prefix = (i == state.current_index) and "" or " "
@@ -185,7 +187,7 @@ local function update_file_list()
end
if #state.entries == 0 then
table.insert(lines, " No changes to review")
table.insert(lines, ui_prompts.messages.no_changes)
end
vim.bo[state.list_buf].modifiable = true
@@ -276,7 +278,7 @@ function M.apply_approved()
update_diff_view()
if applied_count > 0 then
utils.notify(string.format("Applied %d change(s)", applied_count))
utils.notify(string.format(prompts.review.messages.applied_count, applied_count))
end
return applied_count
@@ -289,7 +291,7 @@ function M.open()
end
if #state.entries == 0 then
utils.notify("No changes to review", vim.log.levels.INFO)
utils.notify(prompts.review.messages.no_changes_short, vim.log.levels.INFO)
return
end

View File

@@ -17,74 +17,9 @@ local M = {}
---@field body string[] Non-import code lines
---@field import_lines table<number, boolean> Map of line numbers that are imports
--- Language-specific import patterns
local import_patterns = {
-- JavaScript/TypeScript
javascript = {
{ pattern = "^%s*import%s+.+%s+from%s+['\"]", multi_line = true },
{ pattern = "^%s*import%s+['\"]", multi_line = false },
{ pattern = "^%s*import%s*{", multi_line = true },
{ pattern = "^%s*import%s*%*", multi_line = true },
{ pattern = "^%s*export%s+{.+}%s+from%s+['\"]", multi_line = true },
{ pattern = "^%s*const%s+%w+%s*=%s*require%(['\"]", multi_line = false },
{ pattern = "^%s*let%s+%w+%s*=%s*require%(['\"]", multi_line = false },
{ pattern = "^%s*var%s+%w+%s*=%s*require%(['\"]", multi_line = false },
},
-- Python
python = {
{ pattern = "^%s*import%s+%w", multi_line = false },
{ pattern = "^%s*from%s+[%w%.]+%s+import%s+", multi_line = true },
},
-- Lua
lua = {
{ pattern = "^%s*local%s+%w+%s*=%s*require%s*%(?['\"]", multi_line = false },
{ pattern = "^%s*require%s*%(?['\"]", multi_line = false },
},
-- Go
go = {
{ pattern = "^%s*import%s+%(?", multi_line = true },
},
-- Rust
rust = {
{ pattern = "^%s*use%s+", multi_line = true },
{ pattern = "^%s*extern%s+crate%s+", multi_line = false },
},
-- C/C++
c = {
{ pattern = "^%s*#include%s*[<\"]", multi_line = false },
},
-- Java/Kotlin
java = {
{ pattern = "^%s*import%s+", multi_line = false },
},
-- Ruby
ruby = {
{ pattern = "^%s*require%s+['\"]", multi_line = false },
{ pattern = "^%s*require_relative%s+['\"]", multi_line = false },
},
-- PHP
php = {
{ pattern = "^%s*use%s+", multi_line = false },
{ pattern = "^%s*require%s+['\"]", multi_line = false },
{ pattern = "^%s*require_once%s+['\"]", multi_line = false },
{ pattern = "^%s*include%s+['\"]", multi_line = false },
{ pattern = "^%s*include_once%s+['\"]", multi_line = false },
},
}
-- Alias common extensions to language configs
import_patterns.ts = import_patterns.javascript
import_patterns.tsx = import_patterns.javascript
import_patterns.jsx = import_patterns.javascript
import_patterns.mjs = import_patterns.javascript
import_patterns.cjs = import_patterns.javascript
import_patterns.py = import_patterns.python
import_patterns.cpp = import_patterns.c
import_patterns.hpp = import_patterns.c
import_patterns.h = import_patterns.c
import_patterns.kt = import_patterns.java
import_patterns.rs = import_patterns.rust
import_patterns.rb = import_patterns.ruby
local utils = require("codetyper.utils")
local languages = require("codetyper.params.agent.languages")
local import_patterns = languages.import_patterns
--- Check if a line is an import statement for the given language
---@param line string
@@ -100,83 +35,13 @@ local function is_import_line(line, patterns)
return false, false
end
--- Check if a line is empty or a comment
---@param line string
---@param filetype string
---@return boolean
local function is_empty_or_comment(line, filetype)
local trimmed = line:match("^%s*(.-)%s*$")
if trimmed == "" then
return true
end
-- Language-specific comment patterns
local comment_patterns = {
lua = { "^%-%-" },
python = { "^#" },
javascript = { "^//", "^/%*", "^%*" },
typescript = { "^//", "^/%*", "^%*" },
go = { "^//", "^/%*", "^%*" },
rust = { "^//", "^/%*", "^%*" },
c = { "^//", "^/%*", "^%*", "^#" },
java = { "^//", "^/%*", "^%*" },
ruby = { "^#" },
php = { "^//", "^/%*", "^%*", "^#" },
}
local patterns = comment_patterns[filetype] or comment_patterns.javascript
for _, pattern in ipairs(patterns) do
if trimmed:match(pattern) then
return true
end
end
return false
end
--- Check if a line ends a multi-line import
---@param line string
---@param filetype string
---@return boolean
local function ends_multiline_import(line, filetype)
-- Check for closing patterns
if filetype == "javascript" or filetype == "typescript" or filetype == "ts" or filetype == "tsx" then
-- ES6 imports end with 'from "..." ;' or just ';' or a line with just '}'
if line:match("from%s+['\"][^'\"]+['\"]%s*;?%s*$") then
return true
end
if line:match("}%s*from%s+['\"]") then
return true
end
if line:match("^%s*}%s*;?%s*$") then
return true
end
if line:match(";%s*$") then
return true
end
elseif filetype == "python" or filetype == "py" then
-- Python single-line import: doesn't end with \, (, or ,
-- Examples: "from typing import List, Dict" or "import os"
if not line:match("\\%s*$") and not line:match("%(%s*$") and not line:match(",%s*$") then
return true
end
-- Python multiline imports end with closing paren
if line:match("%)%s*$") then
return true
end
elseif filetype == "go" then
-- Go multi-line imports end with ')'
if line:match("%)%s*$") then
return true
end
elseif filetype == "rust" or filetype == "rs" then
-- Rust use statements end with ';'
if line:match(";%s*$") then
return true
end
end
return false
return utils.ends_multiline_import(line, filetype)
end
--- Parse code into imports and body
@@ -285,7 +150,7 @@ function M.find_import_section(bufnr, filetype)
if is_multi and not ends_multiline_import(line, filetype) then
in_multiline = true
end
elseif is_empty_or_comment(line, filetype) then
elseif utils.is_empty_or_comment(line, filetype) then
-- Allow gaps in import section
if first_import then
consecutive_non_import = consecutive_non_import + 1
@@ -388,34 +253,11 @@ function M.sort_imports(imports, filetype)
local local_imports = {}
for _, imp in ipairs(imports) do
-- Detect import type based on patterns
local is_local = false
local is_builtin = false
local category = utils.classify_import(imp, filetype)
if filetype == "javascript" or filetype == "typescript" or filetype == "ts" or filetype == "tsx" then
-- Local: starts with . or ..
is_local = imp:match("from%s+['\"]%.") or imp:match("require%(['\"]%.")
-- Node builtin modules
is_builtin = imp:match("from%s+['\"]node:") or imp:match("from%s+['\"]fs['\"]")
or imp:match("from%s+['\"]path['\"]") or imp:match("from%s+['\"]http['\"]")
elseif filetype == "python" or filetype == "py" then
-- Local: relative imports
is_local = imp:match("^from%s+%.") or imp:match("^import%s+%.")
-- Python stdlib (simplified check)
is_builtin = imp:match("^import%s+os") or imp:match("^import%s+sys")
or imp:match("^from%s+os%s+") or imp:match("^from%s+sys%s+")
or imp:match("^import%s+re") or imp:match("^import%s+json")
elseif filetype == "lua" then
-- Local: relative requires
is_local = imp:match("require%(['\"]%.") or imp:match("require%s+['\"]%.")
elseif filetype == "go" then
-- Local: project imports (contain /)
is_local = imp:match("['\"][^'\"]+/[^'\"]+['\"]") and not imp:match("github%.com")
end
if is_builtin then
if category == "builtin" then
table.insert(builtin, imp)
elseif is_local then
elseif category == "local" then
table.insert(local_imports, imp)
else
table.insert(third_party, imp)
@@ -533,7 +375,7 @@ function M.inject(bufnr, code, opts)
local trimmed = line:match("^%s*(.-)%s*$")
-- Skip shebang, docstrings, and initial comments
if trimmed ~= "" and not trimmed:match("^#!")
and not trimmed:match("^['\"]") and not is_empty_or_comment(line, filetype) then
and not trimmed:match("^['\"]") and not utils.is_empty_or_comment(line, filetype) then
insert_at = i - 1
break
end

View File

@@ -13,162 +13,10 @@ local M = {}
---@field action string "replace"|"insert"|"append"|"none"
---@field keywords string[] Keywords that triggered this intent
--- Intent patterns with associated metadata
local intent_patterns = {
-- Complete: fill in missing implementation
complete = {
patterns = {
"complete",
"finish",
"implement",
"fill in",
"fill out",
"stub",
"todo",
"fixme",
},
scope_hint = "function",
action = "replace",
priority = 1,
},
-- Refactor: rewrite existing code
refactor = {
patterns = {
"refactor",
"rewrite",
"restructure",
"reorganize",
"clean up",
"cleanup",
"simplify",
"improve",
},
scope_hint = "function",
action = "replace",
priority = 2,
},
-- Fix: repair bugs or issues
fix = {
patterns = {
"fix",
"repair",
"correct",
"debug",
"solve",
"resolve",
"patch",
"bug",
"error",
"issue",
"update",
"modify",
"change",
"adjust",
"tweak",
},
scope_hint = "function",
action = "replace",
priority = 1,
},
-- Add: insert new code
add = {
patterns = {
"add",
"create",
"insert",
"include",
"append",
"new",
"generate",
"write",
},
scope_hint = nil, -- Could be anywhere
action = "insert",
priority = 3,
},
-- Document: add documentation
document = {
patterns = {
"document",
"comment",
"jsdoc",
"docstring",
"describe",
"annotate",
"type hint",
"typehint",
},
scope_hint = "function",
action = "replace", -- Replace with documented version
priority = 2,
},
-- Test: generate tests
test = {
patterns = {
"test",
"spec",
"unit test",
"integration test",
"coverage",
},
scope_hint = "file",
action = "append",
priority = 3,
},
-- Optimize: improve performance
optimize = {
patterns = {
"optimize",
"performance",
"faster",
"efficient",
"speed up",
"reduce",
"minimize",
},
scope_hint = "function",
action = "replace",
priority = 2,
},
-- Explain: provide explanation (no code change)
explain = {
patterns = {
"explain",
"what does",
"how does",
"why",
"describe",
"walk through",
"understand",
},
scope_hint = "function",
action = "none",
priority = 4,
},
}
--- Scope hint patterns
local scope_patterns = {
["this function"] = "function",
["this method"] = "function",
["the function"] = "function",
["the method"] = "function",
["this class"] = "class",
["the class"] = "class",
["this file"] = "file",
["the file"] = "file",
["this block"] = "block",
["the block"] = "block",
["this"] = nil, -- Use Tree-sitter to determine
["here"] = nil,
}
local params = require("codetyper.params.agent.intent")
local intent_patterns = params.intent_patterns
local scope_patterns = params.scope_patterns
local prompts = require("codetyper.prompts.agent.intent")
--- Detect intent from prompt content
---@param prompt string The prompt content
@@ -249,55 +97,7 @@ end
---@param intent Intent
---@return string
function M.get_prompt_modifier(intent)
local modifiers = {
complete = [[
You are completing an incomplete function.
Return the complete function with all missing parts filled in.
Keep the existing signature unless changes are required.
Output only the code, no explanations.]],
refactor = [[
You are refactoring existing code.
Improve the code structure while maintaining the same behavior.
Keep the function signature unchanged.
Output only the refactored code, no explanations.]],
fix = [[
You are fixing a bug in the code.
Identify and correct the issue while minimizing changes.
Preserve the original intent of the code.
Output only the fixed code, no explanations.]],
add = [[
You are adding new code.
Follow the existing code style and conventions.
Output only the new code to be inserted, no explanations.]],
document = [[
You are adding documentation to the code.
Add appropriate comments/docstrings for the function.
Include parameter types, return types, and description.
Output the complete function with documentation.]],
test = [[
You are generating tests for the code.
Create comprehensive unit tests covering edge cases.
Follow the testing conventions of the project.
Output only the test code, no explanations.]],
optimize = [[
You are optimizing code for performance.
Improve efficiency while maintaining correctness.
Document any significant algorithmic changes.
Output only the optimized code, no explanations.]],
explain = [[
You are explaining code to a developer.
Provide a clear, concise explanation of what the code does.
Include information about the algorithm and any edge cases.
Do not output code, only explanation.]],
}
local modifiers = prompts.modifiers
return modifiers[intent.type] or modifiers.add
end

View File

@@ -6,17 +6,11 @@
local M = {}
local config_params = require("codetyper.params.agent.linter")
local prompts = require("codetyper.prompts.agent.linter")
--- Configuration
local config = {
-- Auto-save file after code injection
auto_save = true,
-- Delay in ms to wait for LSP diagnostics to update
diagnostic_delay_ms = 500,
-- Severity levels to check (1=Error, 2=Warning, 3=Info, 4=Hint)
min_severity = vim.diagnostic.severity.WARN,
-- Auto-offer to fix lint errors
auto_offer_fix = true,
}
local config = config_params.config
--- Diagnostic results for tracking
---@type table<number, table>
@@ -330,7 +324,7 @@ function M.request_ai_fix(bufnr, result)
-- Create fix prompt using inline tag
local fix_prompt = string.format(
"Fix the following linter errors in this code:\n\nERRORS:\n%s\n\nCODE (lines %d-%d):\n%s",
prompts.fix_request,
table.concat(error_list, "\n"),
start_line,
end_line,

View File

@@ -4,6 +4,9 @@
local M = {}
local params = require("codetyper.params.agent.logs")
---@class LogEntry
---@field timestamp string ISO timestamp
---@field level string "info" | "debug" | "request" | "response" | "tool" | "error"
@@ -119,14 +122,7 @@ end
---@param status string "start" | "success" | "error" | "approval"
---@param details? string Additional details
function M.tool(tool_name, status, details)
local icons = {
start = "->",
success = "OK",
error = "ERR",
approval = "??",
approved = "YES",
rejected = "NO",
}
local icons = params.icons
local msg = string.format("[%s] %s", icons[status] or status, tool_name)
if details then
@@ -297,17 +293,11 @@ end
---@return string
function M.format_entry(entry)
-- Claude Code style formatting for thinking/action entries
local thinking_types = { "thinking", "reason", "action", "task", "result" }
local thinking_types = params.thinking_types
local is_thinking = vim.tbl_contains(thinking_types, entry.level)
if is_thinking then
local prefix = ({
thinking = "",
reason = "",
action = "",
task = "",
result = "",
})[entry.level] or ""
local prefix = params.thinking_prefixes[entry.level] or ""
if prefix ~= "" then
return prefix .. " " .. entry.message
@@ -317,18 +307,7 @@ function M.format_entry(entry)
end
-- Traditional log format for other types
local level_prefix = ({
info = "i",
debug = ".",
request = ">",
response = "<",
tool = "T",
error = "!",
warning = "?",
success = "i",
queue = "Q",
patch = "P",
})[entry.level] or "?"
local level_prefix = params.level_icons[entry.level] or "?"
local base = string.format("[%s] %s %s", entry.timestamp, level_prefix, entry.message)
@@ -353,15 +332,9 @@ function M.format_for_chat(entry)
end
-- Claude Code style formatting
local thinking_types = { "thinking", "reason", "action", "task", "result" }
local thinking_types = params.thinking_types
if vim.tbl_contains(thinking_types, entry.level) then
local prefix = ({
thinking = "",
reason = "",
action = "",
task = "",
result = "",
})[entry.level] or ""
local prefix = params.thinking_prefixes[entry.level] or ""
if prefix ~= "" then
return prefix .. " " .. entry.message

View File

@@ -6,6 +6,8 @@
local M = {}
local prompts = require("codetyper.prompts.agent.loop")
---@class AgentMessage
---@field role "system"|"user"|"assistant"|"tool"
---@field content string|table
@@ -345,24 +347,7 @@ end
function M.create(task, opts)
opts = opts or {}
local system_prompt = opts.system_prompt or [[You are a helpful coding assistant with access to tools.
Available tools:
- view: Read file contents
- grep: Search for patterns in files
- glob: Find files by pattern
- edit: Make targeted edits to files
- write: Create or overwrite files
- bash: Execute shell commands
When you need to perform a task:
1. Use tools to gather information
2. Plan your approach
3. Execute changes using appropriate tools
4. Verify the results
Always explain your reasoning before using tools.
When you're done, provide a clear summary of what was accomplished.]]
local system_prompt = opts.system_prompt or prompts.default_system_prompt
M.run(vim.tbl_extend("force", opts, {
system_prompt = system_prompt,
@@ -384,9 +369,7 @@ function M.dispatch(prompt, on_complete, opts)
end)
M.run({
system_prompt = [[You are a research assistant. Your task is to find information and report back.
You have access to: view (read files), grep (search content), glob (find files).
Be thorough and report your findings clearly.]],
system_prompt = prompts.dispatch_prompt,
user_input = prompt,
tools = opts.tools or safe_tools,
max_iterations = opts.max_iterations or 5,

View File

@@ -4,6 +4,9 @@
local M = {}
local params = require("codetyper.params.agent.parser")
---@class ParsedResponse
---@field text string Text content from the response
---@field tool_calls ToolCall[] List of tool calls
@@ -48,11 +51,11 @@ function M.parse_ollama_response(response_text)
local result = {
text = response_text,
tool_calls = {},
stop_reason = "end_turn",
stop_reason = params.defaults.stop_reason,
}
-- Pattern to find JSON tool blocks in fenced code blocks
local fenced_pattern = "```json%s*(%b{})%s*```"
local fenced_pattern = params.patterns.fenced_json
-- Find all fenced JSON blocks
for json_str in response_text:gmatch(fenced_pattern) do
@@ -63,14 +66,14 @@ function M.parse_ollama_response(response_text)
name = parsed.tool,
parameters = parsed.parameters,
})
result.stop_reason = "tool_use"
result.stop_reason = params.defaults.tool_stop_reason
end
end
-- Also try to find inline JSON (not in code blocks)
-- Pattern for {"tool": "...", "parameters": {...}}
if #result.tool_calls == 0 then
local inline_pattern = '(%{"tool"%s*:%s*"[^"]+"%s*,%s*"parameters"%s*:%s*%b{}%})'
local inline_pattern = params.patterns.inline_json
for json_str in response_text:gmatch(inline_pattern) do
local ok, parsed = pcall(vim.json.decode, json_str)
if ok and parsed.tool and parsed.parameters then
@@ -79,15 +82,15 @@ function M.parse_ollama_response(response_text)
name = parsed.tool,
parameters = parsed.parameters,
})
result.stop_reason = "tool_use"
result.stop_reason = params.defaults.tool_stop_reason
end
end
end
-- Clean tool JSON from displayed text
if #result.tool_calls > 0 then
result.text = result.text:gsub("```json%s*%b{}%s*```", "[Tool call]")
result.text = result.text:gsub('%{"tool"%s*:%s*"[^"]+"%s*,%s*"parameters"%s*:%s*%b{}%}', "[Tool call]")
result.text = result.text:gsub(params.patterns.fenced_json, params.defaults.replacement_text)
result.text = result.text:gsub(params.patterns.inline_json, params.defaults.replacement_text)
end
return result

View File

@@ -7,6 +7,9 @@
local M = {}
local params = require("codetyper.params.agent.patch")
--- Lazy load inject module to avoid circular requires
local function get_inject_module()
return require("codetyper.agent.inject")
@@ -23,12 +26,7 @@ local function get_conflict_module()
end
--- Configuration for patch behavior
local config = {
-- Use conflict markers instead of direct apply (allows interactive review)
use_conflict_mode = true,
-- Auto-jump to first conflict after applying
auto_jump_to_conflict = true,
}
local config = params.config
---@class BufferSnapshot
---@field bufnr number Buffer number

View File

@@ -10,6 +10,8 @@ local M = {}
---@field allow_list table<string, boolean> Patterns always allowed
---@field deny_list table<string, boolean> Patterns always denied
local params = require("codetyper.params.agent.permissions")
local state = {
session_allowed = {},
allow_list = {},
@@ -17,59 +19,10 @@ local state = {
}
--- Dangerous command patterns that should never be auto-allowed
local DANGEROUS_PATTERNS = {
"^rm%s+%-rf",
"^rm%s+%-r%s+/",
"^rm%s+/",
"^sudo%s+rm",
"^chmod%s+777",
"^chmod%s+%-R",
"^chown%s+%-R",
"^dd%s+",
"^mkfs",
"^fdisk",
"^format",
":.*>%s*/dev/",
"^curl.*|.*sh",
"^wget.*|.*sh",
"^eval%s+",
"`;.*`",
"%$%(.*%)",
"fork%s*bomb",
}
local DANGEROUS_PATTERNS = params.dangerous_patterns
--- Safe command patterns that can be auto-allowed
local SAFE_PATTERNS = {
"^ls%s",
"^ls$",
"^cat%s",
"^head%s",
"^tail%s",
"^grep%s",
"^find%s",
"^pwd$",
"^echo%s",
"^wc%s",
"^which%s",
"^type%s",
"^file%s",
"^stat%s",
"^git%s+status",
"^git%s+log",
"^git%s+diff",
"^git%s+branch",
"^git%s+show",
"^npm%s+list",
"^npm%s+ls",
"^npm%s+outdated",
"^yarn%s+list",
"^cargo%s+check",
"^cargo%s+test",
"^go%s+test",
"^go%s+build",
"^make%s+test",
"^make%s+check",
}
local SAFE_PATTERNS = params.safe_patterns
---@alias PermissionLevel "allow"|"allow_session"|"allow_list"|"reject"

View File

@@ -11,6 +11,7 @@ local patch = require("codetyper.agent.patch")
local worker = require("codetyper.agent.worker")
local confidence_mod = require("codetyper.agent.confidence")
local context_modal = require("codetyper.agent.context_modal")
local params = require("codetyper.params.agent.scheduler")
-- Setup context modal cleanup on exit
context_modal.setup()
@@ -21,15 +22,7 @@ local state = {
timer = nil,
poll_interval = 100, -- ms
paused = false,
config = {
enabled = true,
ollama_scout = true,
escalation_threshold = 0.7,
max_concurrent = 2,
completion_delay_ms = 100,
apply_delay_ms = 5000, -- Wait before applying code
remote_provider = "copilot", -- Default fallback provider
},
config = params.config,
}
--- Autocommand group for injection timing

View File

@@ -14,62 +14,10 @@ local M = {}
---@field name string|nil Name of the function/class if available
--- Node types that represent function-like scopes per language
local function_nodes = {
-- Lua
["function_declaration"] = "function",
["function_definition"] = "function",
["local_function"] = "function",
["function"] = "function",
-- JavaScript/TypeScript
["function_declaration"] = "function",
["function_expression"] = "function",
["arrow_function"] = "function",
["method_definition"] = "method",
["function"] = "function",
-- Python
["function_definition"] = "function",
["async_function_definition"] = "function",
-- Go
["function_declaration"] = "function",
["method_declaration"] = "method",
-- Rust
["function_item"] = "function",
["impl_item"] = "method",
-- Ruby
["method"] = "method",
["singleton_method"] = "method",
-- Java/C#
["method_declaration"] = "method",
["constructor_declaration"] = "method",
-- C/C++
["function_definition"] = "function",
}
--- Node types that represent class-like scopes
local class_nodes = {
["class_declaration"] = "class",
["class_definition"] = "class",
["class"] = "class",
["struct_item"] = "class",
["impl_item"] = "class",
["interface_declaration"] = "class",
["module"] = "class",
}
--- Node types that represent block scopes
local block_nodes = {
["block"] = "block",
["statement_block"] = "block",
["compound_statement"] = "block",
["do_block"] = "block",
}
local params = require("codetyper.params.agent.scope")
local function_nodes = params.function_nodes
local class_nodes = params.class_nodes
local block_nodes = params.block_nodes
--- Check if Tree-sitter is available for buffer
---@param bufnr number

View File

@@ -6,6 +6,8 @@
local M = {}
local params = require("codetyper.params.agent.search_replace").patterns
---@class SearchReplaceBlock
---@field search string The text to search for
---@field replace string The text to replace with
@@ -48,7 +50,7 @@ function M.parse_blocks(response)
local blocks = {}
-- Try dash-style format: ------- SEARCH ... ======= ... +++++++ REPLACE
for search, replace in response:gmatch("%-%-%-%-%-%-%-?%s*SEARCH%s*\n(.-)\n=======%s*\n(.-)\n%+%+%+%+%+%+%+?%s*REPLACE") do
for search, replace in response:gmatch(params.dash_style) do
table.insert(blocks, { search = search, replace = replace })
end
@@ -57,7 +59,7 @@ function M.parse_blocks(response)
end
-- Try claude-style format: <<<<<<< SEARCH ... ======= ... >>>>>>> REPLACE
for search, replace in response:gmatch("<<<<<<<[%s]*SEARCH%s*\n(.-)\n=======%s*\n(.-)\n>>>>>>>[%s]*REPLACE") do
for search, replace in response:gmatch(params.claude_style) do
table.insert(blocks, { search = search, replace = replace })
end
@@ -66,7 +68,7 @@ function M.parse_blocks(response)
end
-- Try simple format: [SEARCH] ... [REPLACE] ... [END]
for search, replace in response:gmatch("%[SEARCH%]%s*\n(.-)\n%[REPLACE%]%s*\n(.-)\n%[END%]") do
for search, replace in response:gmatch(params.simple_style) do
table.insert(blocks, { search = search, replace = replace })
end
@@ -75,7 +77,7 @@ function M.parse_blocks(response)
end
-- Try markdown diff format: ```diff ... ```
local diff_block = response:match("```diff\n(.-)\n```")
local diff_block = response:match(params.diff_block)
if diff_block then
local old_lines = {}
local new_lines = {}

View File

@@ -5,144 +5,7 @@
local M = {}
--- Tool definitions in a provider-agnostic format
M.definitions = {
read_file = {
name = "read_file",
description = "Read the contents of a file at the specified path",
parameters = {
type = "object",
properties = {
path = {
type = "string",
description = "Absolute or relative path to the file to read",
},
},
required = { "path" },
},
},
edit_file = {
name = "edit_file",
description = "Edit a file by replacing specific content. Provide the exact content to find and the replacement.",
parameters = {
type = "object",
properties = {
path = {
type = "string",
description = "Path to the file to edit",
},
find = {
type = "string",
description = "Exact content to find (must match exactly, including whitespace)",
},
replace = {
type = "string",
description = "Content to replace with",
},
},
required = { "path", "find", "replace" },
},
},
write_file = {
name = "write_file",
description = "Write content to a file, creating it if it doesn't exist or overwriting if it does",
parameters = {
type = "object",
properties = {
path = {
type = "string",
description = "Path to the file to write",
},
content = {
type = "string",
description = "Complete file content to write",
},
},
required = { "path", "content" },
},
},
bash = {
name = "bash",
description = "Execute a bash command and return the output. Use for git, npm, build tools, etc.",
parameters = {
type = "object",
properties = {
command = {
type = "string",
description = "The bash command to execute",
},
timeout = {
type = "number",
description = "Timeout in milliseconds (default: 30000)",
},
},
required = { "command" },
},
},
delete_file = {
name = "delete_file",
description = "Delete a file from the filesystem. Use with caution - requires explicit user approval.",
parameters = {
type = "object",
properties = {
path = {
type = "string",
description = "Path to the file to delete",
},
reason = {
type = "string",
description = "Reason for deleting this file (shown to user for approval)",
},
},
required = { "path", "reason" },
},
},
list_directory = {
name = "list_directory",
description = "List files and directories in a path. Use to explore project structure.",
parameters = {
type = "object",
properties = {
path = {
type = "string",
description = "Path to the directory to list (defaults to current directory)",
},
recursive = {
type = "boolean",
description = "Whether to list recursively (default: false, max depth: 3)",
},
},
required = {},
},
},
search_files = {
name = "search_files",
description = "Search for files by name pattern or content. Use to find relevant files in the project.",
parameters = {
type = "object",
properties = {
pattern = {
type = "string",
description = "Glob pattern for file names (e.g., '*.lua', 'test_*.py')",
},
content = {
type = "string",
description = "Search for files containing this text",
},
path = {
type = "string",
description = "Directory to search in (defaults to project root)",
},
},
required = {},
},
},
}
M.definitions = require("codetyper.params.agent.tools").definitions
--- Convert tool definitions to Claude API format
---@return table[] Tools in Claude's expected format
@@ -178,8 +41,9 @@ end
--- Convert tool definitions to prompt format for Ollama
---@return string Formatted tool descriptions for system prompt
function M.to_prompt_format()
local prompts = require("codetyper.prompts.agent.tools").instructions
local lines = {
"You have access to the following tools. To use a tool, respond with a JSON block.",
prompts.intro,
"",
}
@@ -198,13 +62,10 @@ function M.to_prompt_format()
table.insert(lines, "---")
table.insert(lines, "")
table.insert(lines, "To call a tool, output a JSON block like this:")
table.insert(lines, "```json")
table.insert(lines, '{"tool": "tool_name", "parameters": {"param1": "value1"}}')
table.insert(lines, "```")
table.insert(lines, prompts.header)
table.insert(lines, prompts.example)
table.insert(lines, "")
table.insert(lines, "After receiving tool results, continue your response or call another tool.")
table.insert(lines, "When you're done, just respond normally without any tool calls.")
table.insert(lines, prompts.footer)
return table.concat(lines, "\n")
end

View File

@@ -6,6 +6,7 @@
local M = {}
local params = require("codetyper.params.agent.worker")
local confidence = require("codetyper.agent.confidence")
---@class WorkerResult
@@ -32,20 +33,7 @@ local confidence = require("codetyper.agent.confidence")
local worker_counter = 0
--- Patterns that indicate LLM needs more context (must be near start of response)
local context_needed_patterns = {
"^%s*i need more context",
"^%s*i'm sorry.-i need more",
"^%s*i apologize.-i need more",
"^%s*could you provide more context",
"^%s*could you please provide more",
"^%s*can you clarify",
"^%s*please provide more context",
"^%s*more information needed",
"^%s*not enough context",
"^%s*i don't have enough",
"^%s*unclear what you",
"^%s*what do you mean by",
}
local context_needed_patterns = params.context_needed_patterns
--- Check if response indicates need for more context
--- Only triggers if the response primarily asks for context (no substantial code)
@@ -196,12 +184,7 @@ end
local active_workers = {}
--- Default timeouts by provider type
local default_timeouts = {
ollama = 30000, -- 30s for local
openai = 60000, -- 60s for remote
gemini = 60000,
copilot = 60000,
}
local default_timeouts = params.default_timeouts
--- Generate worker ID
---@return string

View File

@@ -0,0 +1,40 @@
---@mod codetyper.params.agent.confidence Parameters for confidence scoring
local M = {}
--- Heuristic weights (must sum to 1.0)
M.weights = {
length = 0.15, -- Response length relative to prompt
uncertainty = 0.30, -- Uncertainty phrases
syntax = 0.25, -- Syntax completeness
repetition = 0.15, -- Duplicate lines
truncation = 0.15, -- Incomplete ending
}
--- Uncertainty phrases that indicate low confidence
M.uncertainty_phrases = {
-- English
"i'm not sure",
"i am not sure",
"maybe",
"perhaps",
"might work",
"could work",
"not certain",
"uncertain",
"i think",
"possibly",
"TODO",
"FIXME",
"XXX",
"placeholder",
"implement this",
"fill in",
"your code here",
"...", -- Ellipsis as placeholder
"# TODO",
"// TODO",
"-- TODO",
"/* TODO",
}
return M

View File

@@ -0,0 +1,33 @@
---@mod codetyper.params.agent.conflict Parameters for conflict resolution
local M = {}
--- Configuration defaults
M.config = {
-- Run linter check after accepting AI suggestions
lint_after_accept = true,
-- Auto-fix lint errors without prompting
auto_fix_lint_errors = true,
-- Auto-show menu after injecting conflict
auto_show_menu = true,
-- Auto-show menu for next conflict after resolving one
auto_show_next_menu = true,
}
--- Highlight groups
M.hl_groups = {
current = "CoderConflictCurrent",
current_label = "CoderConflictCurrentLabel",
incoming = "CoderConflictIncoming",
incoming_label = "CoderConflictIncomingLabel",
separator = "CoderConflictSeparator",
hint = "CoderConflictHint",
}
--- Conflict markers
M.markers = {
current_start = "<<<<<<< CURRENT",
separator = "=======",
incoming_end = ">>>>>>> INCOMING",
}
return M

View File

@@ -0,0 +1,48 @@
---@mod codetyper.params.agent.context Parameters for context building
local M = {}
--- Common ignore patterns
M.ignore_patterns = {
"^%.", -- Hidden files/dirs
"node_modules",
"%.git$",
"__pycache__",
"%.pyc$",
"target", -- Rust
"build",
"dist",
"%.o$",
"%.a$",
"%.so$",
"%.min%.",
"%.map$",
}
--- Key files that are important for understanding the project
M.important_files = {
["package.json"] = "Node.js project config",
["Cargo.toml"] = "Rust project config",
["go.mod"] = "Go module config",
["pyproject.toml"] = "Python project config",
["setup.py"] = "Python setup config",
["Makefile"] = "Build configuration",
["CMakeLists.txt"] = "CMake config",
[".gitignore"] = "Git ignore patterns",
["README.md"] = "Project documentation",
["init.lua"] = "Neovim plugin entry",
["plugin.lua"] = "Neovim plugin config",
}
--- Project type detection indicators
M.indicators = {
["package.json"] = { type = "node", language = "javascript/typescript" },
["Cargo.toml"] = { type = "rust", language = "rust" },
["go.mod"] = { type = "go", language = "go" },
["pyproject.toml"] = { type = "python", language = "python" },
["setup.py"] = { type = "python", language = "python" },
["Gemfile"] = { type = "ruby", language = "ruby" },
["pom.xml"] = { type = "maven", language = "java" },
["build.gradle"] = { type = "gradle", language = "java/kotlin" },
}
return M

View File

@@ -0,0 +1,161 @@
---@mod codetyper.params.agent.intent Intent patterns and scope configuration
local M = {}
--- Intent patterns with associated metadata
M.intent_patterns = {
-- Complete: fill in missing implementation
complete = {
patterns = {
"complete",
"finish",
"implement",
"fill in",
"fill out",
"stub",
"todo",
"fixme",
},
scope_hint = "function",
action = "replace",
priority = 1,
},
-- Refactor: rewrite existing code
refactor = {
patterns = {
"refactor",
"rewrite",
"restructure",
"reorganize",
"clean up",
"cleanup",
"simplify",
"improve",
},
scope_hint = "function",
action = "replace",
priority = 2,
},
-- Fix: repair bugs or issues
fix = {
patterns = {
"fix",
"repair",
"correct",
"debug",
"solve",
"resolve",
"patch",
"bug",
"error",
"issue",
"update",
"modify",
"change",
"adjust",
"tweak",
},
scope_hint = "function",
action = "replace",
priority = 1,
},
-- Add: insert new code
add = {
patterns = {
"add",
"create",
"insert",
"include",
"append",
"new",
"generate",
"write",
},
scope_hint = nil, -- Could be anywhere
action = "insert",
priority = 3,
},
-- Document: add documentation
document = {
patterns = {
"document",
"comment",
"jsdoc",
"docstring",
"describe",
"annotate",
"type hint",
"typehint",
},
scope_hint = "function",
action = "replace", -- Replace with documented version
priority = 2,
},
-- Test: generate tests
test = {
patterns = {
"test",
"spec",
"unit test",
"integration test",
"coverage",
},
scope_hint = "file",
action = "append",
priority = 3,
},
-- Optimize: improve performance
optimize = {
patterns = {
"optimize",
"performance",
"faster",
"efficient",
"speed up",
"reduce",
"minimize",
},
scope_hint = "function",
action = "replace",
priority = 2,
},
-- Explain: provide explanation (no code change)
explain = {
patterns = {
"explain",
"what does",
"how does",
"why",
"describe",
"walk through",
"understand",
},
scope_hint = "function",
action = "none",
priority = 4,
},
}
--- Scope hint patterns
M.scope_patterns = {
["this function"] = "function",
["this method"] = "function",
["the function"] = "function",
["the method"] = "function",
["this class"] = "class",
["the class"] = "class",
["this file"] = "file",
["the file"] = "file",
["this block"] = "block",
["the block"] = "block",
["this"] = nil, -- Use Tree-sitter to determine
["here"] = nil,
}
return M

View File

@@ -0,0 +1,87 @@
---@mod codetyper.params.agent.languages Language-specific patterns and configurations
local M = {}
--- Language-specific import patterns
M.import_patterns = {
-- JavaScript/TypeScript
javascript = {
{ pattern = "^%s*import%s+.+%s+from%s+['\"]", multi_line = true },
{ pattern = "^%s*import%s+['\"]", multi_line = false },
{ pattern = "^%s*import%s*{", multi_line = true },
{ pattern = "^%s*import%s*%*", multi_line = true },
{ pattern = "^%s*export%s+{.+}%s+from%s+['\"]", multi_line = true },
{ pattern = "^%s*const%s+%w+%s*=%s*require%(['\"]", multi_line = false },
{ pattern = "^%s*let%s+%w+%s*=%s*require%(['\"]", multi_line = false },
{ pattern = "^%s*var%s+%w+%s*=%s*require%(['\"]", multi_line = false },
},
-- Python
python = {
{ pattern = "^%s*import%s+%w", multi_line = false },
{ pattern = "^%s*from%s+[%w%.]+%s+import%s+", multi_line = true },
},
-- Lua
lua = {
{ pattern = "^%s*local%s+%w+%s*=%s*require%s*%(?['\"]", multi_line = false },
{ pattern = "^%s*require%s*%(?['\"]", multi_line = false },
},
-- Go
go = {
{ pattern = "^%s*import%s+%(?", multi_line = true },
},
-- Rust
rust = {
{ pattern = "^%s*use%s+", multi_line = true },
{ pattern = "^%s*extern%s+crate%s+", multi_line = false },
},
-- C/C++
c = {
{ pattern = "^%s*#include%s*[<\"]", multi_line = false },
},
-- Java/Kotlin
java = {
{ pattern = "^%s*import%s+", multi_line = false },
},
-- Ruby
ruby = {
{ pattern = "^%s*require%s+['\"]", multi_line = false },
{ pattern = "^%s*require_relative%s+['\"]", multi_line = false },
},
-- PHP
php = {
{ pattern = "^%s*use%s+", multi_line = false },
{ pattern = "^%s*require%s+['\"]", multi_line = false },
{ pattern = "^%s*require_once%s+['\"]", multi_line = false },
{ pattern = "^%s*include%s+['\"]", multi_line = false },
{ pattern = "^%s*include_once%s+['\"]", multi_line = false },
},
}
-- Alias common extensions to language configs
M.import_patterns.ts = M.import_patterns.javascript
M.import_patterns.tsx = M.import_patterns.javascript
M.import_patterns.jsx = M.import_patterns.javascript
M.import_patterns.mjs = M.import_patterns.javascript
M.import_patterns.cjs = M.import_patterns.javascript
M.import_patterns.py = M.import_patterns.python
M.import_patterns.cpp = M.import_patterns.c
M.import_patterns.hpp = M.import_patterns.c
M.import_patterns.h = M.import_patterns.c
M.import_patterns.kt = M.import_patterns.java
M.import_patterns.rs = M.import_patterns.rust
M.import_patterns.rb = M.import_patterns.ruby
--- Language-specific comment patterns
M.comment_patterns = {
lua = { "^%-%-" },
python = { "^#" },
javascript = { "^//", "^/%*", "^%*" },
typescript = { "^//", "^/%*", "^%*" },
go = { "^//", "^/%*", "^%*" },
rust = { "^//", "^/%*", "^%*" },
c = { "^//", "^/%*", "^%*", "^#" },
java = { "^//", "^/%*", "^%*" },
ruby = { "^#" },
php = { "^//", "^/%*", "^%*", "^#" },
}
return M

View File

@@ -0,0 +1,15 @@
---@mod codetyper.params.agent.linter Linter configuration
local M = {}
M.config = {
-- Auto-save file after code injection
auto_save = true,
-- Delay in ms to wait for LSP diagnostics to update
diagnostic_delay_ms = 500,
-- Severity levels to check (1=Error, 2=Warning, 3=Info, 4=Hint)
min_severity = vim.diagnostic.severity.WARN,
-- Auto-offer to fix lint errors
auto_offer_fix = true,
}
return M

View File

@@ -0,0 +1,36 @@
---@mod codetyper.params.agent.logs Log parameters
local M = {}
M.icons = {
start = "->",
success = "OK",
error = "ERR",
approval = "??",
approved = "YES",
rejected = "NO",
}
M.level_icons = {
info = "i",
debug = ".",
request = ">",
response = "<",
tool = "T",
error = "!",
warning = "?",
success = "i",
queue = "Q",
patch = "P",
}
M.thinking_types = { "thinking", "reason", "action", "task", "result" }
M.thinking_prefixes = {
thinking = "",
reason = "",
action = "",
task = "",
result = "",
}
return M

View File

@@ -0,0 +1,15 @@
---@mod codetyper.params.agent.parser Parser regex patterns
local M = {}
M.patterns = {
fenced_json = "```json%s*(%b{})%s*```",
inline_json = '(%{"tool"%s*:%s*"[^"]+"%s*,%s*"parameters"%s*:%s*%b{}%})',
}
M.defaults = {
stop_reason = "end_turn",
tool_stop_reason = "tool_use",
replacement_text = "[Tool call]",
}
return M

View File

@@ -0,0 +1,12 @@
---@mod codetyper.params.agent.patch Patch configuration
local M = {}
M.config = {
snapshot_range = 5, -- Lines above/below prompt to snapshot
clean_interval_ms = 60000, -- Check for stale patches every minute
max_age_ms = 3600000, -- 1 hour TTL
staleness_check = true,
use_search_replace_parser = true, -- Enable new parsing logic
}
return M

View File

@@ -0,0 +1,47 @@
---@mod codetyper.params.agent.permissions Dangerous and safe command patterns
local M = {}
--- Dangerous command patterns that should never be auto-allowed
M.dangerous_patterns = {
"^rm%s+%-rf",
"^rm%s+%-r%s+/",
"^rm%s+/",
"^sudo%s+rm",
"^chmod%s+777",
"^chmod%s+%-R",
"^chown%s+%-R",
"^dd%s+",
"^mkfs",
"^fdisk",
"^format",
":.*>%s*/dev/",
"^curl.*|.*sh",
"^wget.*|.*sh",
"^eval%s+",
"`;.*`",
"%$%(.*%)",
"fork%s*bomb",
}
--- Safe command patterns that can be auto-allowed
M.safe_patterns = {
"^ls%s",
"^ls$",
"^cat%s",
"^head%s",
"^tail%s",
"^grep%s",
"^find%s",
"^pwd$",
"^echo%s",
"^wc%s",
"^git%s+status",
"^git%s+diff",
"^git%s+log",
"^git%s+show",
"^git%s+branch",
"^git%s+checkout",
"^git%s+add", -- Generally safe if reviewing changes
}
return M

View File

@@ -0,0 +1,14 @@
---@mod codetyper.params.agent.scheduler Scheduler configuration
local M = {}
M.config = {
enabled = true,
ollama_scout = true,
escalation_threshold = 0.7,
max_concurrent = 2,
completion_delay_ms = 100,
apply_delay_ms = 5000, -- Wait before applying code
remote_provider = "copilot", -- Default fallback provider
}
return M

View File

@@ -0,0 +1,72 @@
---@mod codetyper.params.agent.scope Tree-sitter scope mappings
local M = {}
--- Node types that represent function-like scopes per language
M.function_nodes = {
-- Lua
["function_declaration"] = "function",
["function_definition"] = "function",
["local_function"] = "function",
["function"] = "function",
-- JavaScript/TypeScript
["function_declaration"] = "function",
["function_expression"] = "function",
["arrow_function"] = "function",
["method_definition"] = "method",
["function"] = "function",
-- Python
["function_definition"] = "function",
["lambda"] = "function",
-- Go
["function_declaration"] = "function",
["method_declaration"] = "method",
["func_literal"] = "function",
-- Rust
["function_item"] = "function",
["closure_expression"] = "function",
-- C/C++
["function_definition"] = "function",
["lambda_expression"] = "function",
-- Java
["method_declaration"] = "method",
["constructor_declaration"] = "method",
["lambda_expression"] = "function",
-- Ruby
["method"] = "method",
["singleton_method"] = "method",
["lambda"] = "function",
["block"] = "function",
-- PHP
["function_definition"] = "function",
["method_declaration"] = "method",
["arrow_function"] = "function",
}
--- Node types that represent class-like scopes
M.class_nodes = {
["class_declaration"] = "class",
["class_definition"] = "class",
["struct_declaration"] = "class",
["impl_item"] = "class", -- Rust config
["interface_declaration"] = "class",
["trait_item"] = "class",
}
--- Node types that represent block scopes
M.block_nodes = {
["block"] = "block",
["do_statement"] = "block", -- Lua
["if_statement"] = "block",
["for_statement"] = "block",
["while_statement"] = "block",
}
return M

View File

@@ -0,0 +1,11 @@
---@mod codetyper.params.agent.search_replace Search/Replace patterns
local M = {}
M.patterns = {
dash_style = "%-%-%-%-%-%-%-?%s*SEARCH%s*\n(.-)\n=======%s*\n(.-)\n%+%+%+%+%+%+%+?%s*REPLACE",
claude_style = "<<<<<<<[%s]*SEARCH%s*\n(.-)\n=======%s*\n(.-)\n>>>>>>>[%s]*REPLACE",
simple_style = "%[SEARCH%]%s*\n(.-)\n%[REPLACE%]%s*\n(.-)\n%[END%]",
diff_block = "```diff\n(.-)\n```",
}
return M

View File

@@ -0,0 +1,147 @@
---@mod codetyper.params.agent.tools Tool definitions
local M = {}
--- Tool definitions in a provider-agnostic format
M.definitions = {
read_file = {
name = "read_file",
description = "Read the contents of a file at the specified path",
parameters = {
type = "object",
properties = {
path = {
type = "string",
description = "The path to the file to read",
},
start_line = {
type = "number",
description = "Optional start line number (1-indexed)",
},
end_line = {
type = "number",
description = "Optional end line number (1-indexed)",
},
},
required = { "path" },
},
},
edit_file = {
name = "edit_file",
description = "Edit a file by replacing specific content. Provide the exact content to find and the replacement.",
parameters = {
type = "object",
properties = {
path = {
type = "string",
description = "The path to the file to edit",
},
find = {
type = "string",
description = "The exact content to replace",
},
replace = {
type = "string",
description = "The new content",
},
},
required = { "path", "find", "replace" },
},
},
write_file = {
name = "write_file",
description = "Write content to a file, creating it if it doesn't exist or overwriting if it does",
parameters = {
type = "object",
properties = {
path = {
type = "string",
description = "The path to the file to write",
},
content = {
type = "string",
description = "The content to write",
},
},
required = { "path", "content" },
},
},
bash = {
name = "bash",
description = "Execute a bash command and return the output. Use for git, npm, build tools, etc.",
parameters = {
type = "object",
properties = {
command = {
type = "string",
description = "The bash command to execute",
},
},
required = { "command" },
},
},
delete_file = {
name = "delete_file",
description = "Delete a file",
parameters = {
type = "object",
properties = {
path = {
type = "string",
description = "The path to the file to delete",
},
reason = {
type = "string",
description = "Reason for deletion",
},
},
required = { "path", "reason" },
},
},
list_directory = {
name = "list_directory",
description = "List files and directories in a path",
parameters = {
type = "object",
properties = {
path = {
type = "string",
description = "The path to list",
},
recursive = {
type = "boolean",
description = "Whether to list recursively",
},
},
required = { "path" },
},
},
search_files = {
name = "search_files",
description = "Search for files by name/glob pattern or content",
parameters = {
type = "object",
properties = {
pattern = {
type = "string",
description = "Glob pattern to search for filenames",
},
content = {
type = "string",
description = "Content string to search for within files",
},
path = {
type = "string",
description = "The root path to start search",
},
},
},
},
}
return M

View File

@@ -0,0 +1,30 @@
---@mod codetyper.params.agent.worker Worker configuration and patterns
local M = {}
--- Patterns that indicate LLM needs more context (must be near start of response)
M.context_needed_patterns = {
"I need to see",
"Could you provide",
"Please provide",
"Can you show",
"don't have enough context",
"need more information",
"cannot see the definition",
"missing the implementation",
"I would need to check",
"please share",
"Please upload",
"could not find",
}
--- Default timeouts by provider type
M.default_timeouts = {
openai = 60000, -- 60s
anthropic = 90000, -- 90s
google = 60000, -- 60s
ollama = 120000, -- 120s (local models can be slower)
copilot = 60000, -- 60s
default = 60000,
}
return M

View File

@@ -109,4 +109,33 @@ Include:
Do NOT restate tool output verbatim.
]]
--- Text-based tool calling instructions
M.tool_instructions_text = [[
## Available Tools
Call tools by outputting JSON in this format:
```json
{"tool": "tool_name", "arguments": {...}}
```
]]
--- Initial greeting when files are provided
M.initial_assistant_message = "I've reviewed the provided files. What would you like me to do?"
--- Format prefixes for text-based models
M.text_user_prefix = "User: "
M.text_assistant_prefix = "Assistant: "
--- Format file context
---@param files string[] Paths
---@return string Formatted context
function M.format_file_context(files)
local context = "# Initial Files\n"
for _, file_path in ipairs(files) do
local content = table.concat(vim.fn.readfile(file_path) or {}, "\n")
context = context .. string.format("\n## %s\n```\n%s\n```\n", file_path, content)
end
return context
end
return M

View File

@@ -0,0 +1,66 @@
---@mod codetyper.prompts.agent.diff Prompts and UI strings for diff view and bash approval
local M = {}
--- Bash approval dialog strings
M.bash_approval = {
title = " BASH COMMAND APPROVAL",
divider = " " .. string.rep("", 56),
command_label = " Command:",
warning_prefix = " ⚠️ WARNING: ",
options = {
" [y] Allow once - Execute this command",
" [s] Allow this session - Auto-allow until restart",
" [a] Add to allow list - Always allow this command",
" [n] Reject - Cancel execution",
},
cancel_hint = " Press key to choose | [q] or [Esc] to cancel",
}
--- Diff view help message
M.diff_help = {
{ "Diff: ", "Normal" },
{ "{path}", "Directory" },
{ " | ", "Normal" },
{ "y/<CR>", "Keyword" },
{ " approve ", "Normal" },
{ "n/q/<Esc>", "Keyword" },
{ " reject ", "Normal" },
{ "<Tab>", "Keyword" },
{ " switch panes", "Normal" },
}
--- Review UI interface strings
M.review = {
diff_header = {
top = "╭─ %s %s %s ─────────────────────────────────────",
path = "│ %s",
op = "│ Operation: %s",
status = "│ Status: %s",
bottom = "╰────────────────────────────────────────────────────",
},
list_menu = {
top = "╭─ Changes (%s) ──────────╮",
items = {
"│ │",
"│ j/k: navigate │",
"│ Enter: view diff │",
"│ a: approve r: reject │",
"│ A: approve all │",
"│ q: close │",
},
bottom = "╰──────────────────────────────╯",
},
status = {
applied = "Applied",
approved = "Approved",
pending = "Pending",
},
messages = {
no_changes = " No changes to review",
no_changes_short = "No changes to review",
applied_count = "Applied %d change(s)",
},
}
return M

View File

@@ -0,0 +1,53 @@
---@mod codetyper.prompts.agent.intent Intent-specific system prompts
local M = {}
M.modifiers = {
complete = [[
You are completing an incomplete function.
Return the complete function with all missing parts filled in.
Keep the existing signature unless changes are required.
Output only the code, no explanations.]],
refactor = [[
You are refactoring existing code.
Improve the code structure while maintaining the same behavior.
Keep the function signature unchanged.
Output only the refactored code, no explanations.]],
fix = [[
You are fixing a bug in the code.
Identify and correct the issue while minimizing changes.
Preserve the original intent of the code.
Output only the fixed code, no explanations.]],
add = [[
You are adding new code.
Follow the existing code style and conventions.
Output only the new code to be inserted, no explanations.]],
document = [[
You are adding documentation to the code.
Add appropriate comments/docstrings for the function.
Include parameter types, return types, and description.
Output the complete function with documentation.]],
test = [[
You are generating tests for the code.
Create comprehensive unit tests covering edge cases.
Follow the testing conventions of the project.
Output only the test code, no explanations.]],
optimize = [[
You are optimizing code for performance.
Improve efficiency while maintaining correctness.
Document any significant algorithmic changes.
Output only the optimized code, no explanations.]],
explain = [[
You are explaining code to a developer.
Provide a clear, concise explanation of what the code does.
Include information about the algorithm and any edge cases.
Do not output code, only explanation.]],
}
return M

View File

@@ -0,0 +1,13 @@
---@mod codetyper.prompts.agent.linter Linter prompts
local M = {}
M.fix_request = [[
Fix the following linter errors in this code:
ERRORS:
%s
CODE (lines %d-%d):
%s]]
return M

View File

@@ -0,0 +1,55 @@
---@mod codetyper.prompts.agent.loop Agent Loop prompts
local M = {}
M.default_system_prompt = [[You are a helpful coding assistant with access to tools.
Available tools:
- view: Read file contents
- grep: Search for patterns in files
- glob: Find files by pattern
- edit: Make targeted edits to files
- write: Create or overwrite files
- bash: Execute shell commands
When you need to perform a task:
1. Use tools to gather information
2. Plan your approach
3. Execute changes using appropriate tools
4. Verify the results
Always explain your reasoning before using tools.
When you're done, provide a clear summary of what was accomplished.]]
M.dispatch_prompt = [[You are a research assistant. Your task is to find information and report back.
You have access to: view (read files), grep (search content), glob (find files).
Be thorough and report your findings clearly.]]
### File Operations
- **read_file**: Read any file. Parameters: path (string)
- **write_file**: Create or overwrite files. Parameters: path (string), content (string)
- **edit_file**: Modify existing files. Parameters: path (string), find (string), replace (string)
- **list_directory**: List files and directories. Parameters: path (string, optional), recursive (boolean, optional)
- **search_files**: Find files. Parameters: pattern (string), content (string), path (string)
- **delete_file**: Delete a file. Parameters: path (string), reason (string)
### Shell Commands
- **bash**: Run shell commands. Parameters: command (string)
## WORKFLOW
1. **Analyze**: Understand the user's request.
2. **Explore**: Use `list_directory`, `search_files`, or `read_file` to find relevant files.
3. **Plan**: Think about what needs to be changed.
4. **Execute**: Use `edit_file`, `write_file`, or `bash` to apply changes.
5. **Verify**: You can check files after editing.
Always verify context before making changes.
]]
M.dispatch_prompt = [[
You are a research assistant. Your job is to explore the codebase and answer the user's question or find specific information.
You have access to: view (read files), grep (search content), glob (find files).
Be thorough and report your findings clearly.
]]
return M

View File

@@ -0,0 +1,14 @@
---@mod codetyper.prompts.agent.modal Prompts and UI strings for context modal
local M = {}
--- Modal UI strings
M.ui = {
files_header = { "", "-- No files detected in LLM response --" },
llm_response_header = "-- LLM Response: --",
suggested_commands_header = "-- Suggested commands: --",
commands_hint = "-- Press <leader><n> to run a command, or <leader>r to run all --",
input_header = "-- Enter additional context below (Ctrl-Enter to submit, Esc to cancel) --",
project_inspect_header = { "", "-- Project inspection results --" },
}
return M

View File

@@ -0,0 +1,12 @@
---@mod codetyper.prompts.agent.scheduler Scheduler prompts
local M = {}
M.retry_context = [[
You requested more context for this task.
Here is the additional information:
%s
Please restart the task with this new context.
]]
return M

View File

@@ -0,0 +1,18 @@
---@mod codetyper.prompts.agent.tools Tool system prompts
local M = {}
M.instructions = {
intro = "You have access to the following tools. To use a tool, respond with a JSON block.",
header = "To call a tool, output a JSON block like this:",
example = [[
```json
{"tool": "tool_name", "parameters": {"param1": "value1"}}
```
]],
footer = [[
After receiving tool results, continue your response or call another tool.
When you're done, just respond normally without any tool calls.
]],
}
return M

View File

@@ -0,0 +1,58 @@
---@mod codetyper.prompts.agents.personas Built-in agent personas
local M = {}
M.builtin = {
coder = {
name = "coder",
description = "Full-featured coding agent with file modification capabilities",
system_prompt = [[You are an expert software engineer. You have access to tools to read, write, and modify files.
## Your Capabilities
- Read files to understand the codebase
- Search for patterns with grep and glob
- Create new files with write tool
- Edit existing files with precise replacements
- Execute shell commands for builds and tests
## Guidelines
1. Always read relevant files before making changes
2. Make minimal, focused changes
3. Follow existing code style and patterns
4. Create tests when adding new functionality
5. Verify changes work by running tests or builds
## Important Rules
- NEVER guess file contents - always read first
- Make precise edits using exact string matching
- Explain your reasoning before making changes
- If unsure, ask for clarification]],
tools = { "view", "edit", "write", "grep", "glob", "bash" },
},
planner = {
name = "planner",
description = "Planning agent - read-only, helps design implementations",
system_prompt = [[You are a software architect. Analyze codebases and create implementation plans.
You can read files and search the codebase, but cannot modify files.
Your role is to:
1. Understand the existing architecture
2. Identify relevant files and patterns
3. Create step-by-step implementation plans
4. Suggest which files to modify and how
Be thorough in your analysis before making recommendations.]],
tools = { "view", "grep", "glob" },
},
explorer = {
name = "explorer",
description = "Exploration agent - quickly find information in codebase",
system_prompt = [[You are a codebase exploration assistant. Find information quickly and report back.
Your goal is to efficiently search and summarize findings.
Use glob to find files, grep to search content, and view to read specific files.
Be concise and focused in your responses.]],
tools = { "view", "grep", "glob" },
},
}
return M

View File

@@ -0,0 +1,51 @@
---@mod codetyper.prompts.agents.templates Agent and Rule templates
local M = {}
M.agent = [[---
description: Example custom agent
tools: view,grep,glob,edit,write
model:
---
# Custom Agent
You are a custom coding agent. Describe your specialized behavior here.
## Your Role
- Define what this agent specializes in
- List specific capabilities
## Guidelines
- Add agent-specific rules
- Define coding standards to follow
## Examples
Provide examples of how to handle common tasks.
]]
M.rule = [[# Code Style
Follow these coding standards:
## General
- Use consistent indentation (tabs or spaces based on project)
- Keep lines under 100 characters
- Add comments for complex logic
## Naming Conventions
- Use descriptive variable names
- Functions should be verbs (e.g., getUserData, calculateTotal)
- Constants in UPPER_SNAKE_CASE
## Testing
- Write tests for new functionality
- Aim for >80% code coverage
- Test edge cases
## Documentation
- Document public APIs
- Include usage examples
- Keep docs up to date with code
]]
return M

View File

@@ -2,6 +2,14 @@
local M = {}
--- Generate a unique ID
---@param prefix? string Prefix for the ID (default: "id")
---@return string Unique ID
function M.generate_id(prefix)
prefix = prefix or "id"
return prefix .. "_" .. string.format("%x", os.time()) .. "_" .. string.format("%x", math.random(0, 0xFFFF))
end
--- Get the project root directory
---@return string|nil Root directory path or nil if not found
function M.get_project_root()
@@ -194,4 +202,147 @@ function M.get_visual_selection()
}
end
--- Check bracket balance for common languages
---@param response string
---@return boolean balanced
function M.check_brackets(response)
local pairs = {
["{"] = "}",
["["] = "]",
["("] = ")",
}
local stack = {}
for char in response:gmatch(".") do
if pairs[char] then
table.insert(stack, pairs[char])
elseif char == "}" or char == "]" or char == ")" then
if #stack == 0 or stack[#stack] ~= char then
return false
end
table.remove(stack)
end
end
return #stack == 0
end
--- Simple hash function for content
---@param content string
---@return string
function M.hash_content(content)
local hash = vim.fn.sha256(content)
-- If sha256 returns hex string, format %x might be wrong if it expects number?
-- vim.fn.sha256 returns a hex string already.
return hash
end
--- Check if a line is empty or a comment
---@param line string
---@param filetype string
---@return boolean
function M.is_empty_or_comment(line, filetype)
local trimmed = line:match("^%s*(.-)%s*$")
if trimmed == "" then
return true
end
local ok, languages = pcall(require, "codetyper.params.agent.languages")
if not ok then return false end
local patterns = languages.comment_patterns[filetype] or languages.comment_patterns.javascript
for _, pattern in ipairs(patterns) do
if trimmed:match(pattern) then
return true
end
end
return false
end
--- Classify an import as "builtin", "local", or "third_party"
---@param imp string The import statement
---@param filetype string The filetype
---@return string category "builtin"|"local"|"third_party"
function M.classify_import(imp, filetype)
local is_local = false
local is_builtin = false
if filetype == "javascript" or filetype == "typescript" or filetype == "ts" or filetype == "tsx" then
-- Local: starts with . or ..
is_local = imp:match("from%s+['\"]%.") or imp:match("require%(['\"]%.")
-- Node builtin modules
is_builtin = imp:match("from%s+['\"]node:") or imp:match("from%s+['\"]fs['\"]")
or imp:match("from%s+['\"]path['\"]") or imp:match("from%s+['\"]http['\"]")
elseif filetype == "python" or filetype == "py" then
-- Local: relative imports
is_local = imp:match("^from%s+%.") or imp:match("^import%s+%.")
-- Python stdlib (simplified check)
is_builtin = imp:match("^import%s+os") or imp:match("^import%s+sys")
or imp:match("^from%s+os%s+") or imp:match("^from%s+sys%s+")
or imp:match("^import%s+re") or imp:match("^import%s+json")
elseif filetype == "lua" then
-- Local: relative requires
is_local = imp:match("require%(['\"]%.") or imp:match("require%s+['\"]%.")
elseif filetype == "go" then
-- Local: project imports (contain /)
is_local = imp:match("['\"][^'\"]+/[^'\"]+['\"]") and not imp:match("github%.com")
end
if is_builtin then
return "builtin"
elseif is_local then
return "local"
else
return "third_party"
end
end
--- Check if a line ends a multi-line import
---@param line string
---@param filetype string
---@return boolean
function M.ends_multiline_import(line, filetype)
-- Check for closing patterns
if filetype == "javascript" or filetype == "typescript" or filetype == "ts" or filetype == "tsx" then
-- ES6 imports end with 'from "..." ;' or just ';' or a line with just '}'
if line:match("from%s+['\"][^'\"]+['\"]%s*;?%s*$") then
return true
end
if line:match("}%s*from%s+['\"]") then
return true
end
if line:match("^%s*}%s*;?%s*$") then
return true
end
if line:match(";%s*$") then
return true
end
elseif filetype == "python" or filetype == "py" then
-- Python single-line import: doesn't end with \, (, or ,
-- Examples: "from typing import List, Dict" or "import os"
if not line:match("\\%s*$") and not line:match("%(%s*$") and not line:match(",%s*$") then
return true
end
-- Python multiline imports end with closing paren
if line:match("%)%s*$") then
return true
end
elseif filetype == "go" then
-- Go multi-line imports end with ')'
if line:match("%)%s*$") then
return true
end
elseif filetype == "rust" or filetype == "rs" then
-- Rust use statements end with ';'
if line:match(";%s*$") then
return true
end
end
return false
end
return M