From 0ff77954db0d24a27e1441d4f8a5d4d0eb8abfcb Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Fri, 31 Jan 2025 10:31:09 +0100 Subject: [PATCH] feat: remove unneeded debug command approach --- lua/chatgpt_nvim/config.lua | 9 +-- lua/chatgpt_nvim/init.lua | 126 +++-------------------------- lua/chatgpt_nvim/prompts.lua | 117 +++++++++------------------ lua/chatgpt_nvim/tools/manager.lua | 13 +-- 4 files changed, 63 insertions(+), 202 deletions(-) diff --git a/lua/chatgpt_nvim/config.lua b/lua/chatgpt_nvim/config.lua index 1465b6a..4c47492 100644 --- a/lua/chatgpt_nvim/config.lua +++ b/lua/chatgpt_nvim/config.lua @@ -50,12 +50,12 @@ function M.load() improved_debug = false, enable_chunking = false, enable_step_by_step = true, - enable_debug_commands = false, - -- New table for tool auto-accept configuration + -- Removed enable_debug_commands tool_auto_accept = { readFile = false, editFile = false, + replace_in_file = false, executeCommand = false, } } @@ -106,9 +106,6 @@ function M.load() if type(result.enable_step_by_step) == "boolean" then config.enable_step_by_step = result.enable_step_by_step end - if type(result.enable_debug_commands) == "boolean" then - config.enable_debug_commands = result.enable_debug_commands - end -- Load tool_auto_accept if present if type(result.tool_auto_accept) == "table" then @@ -124,7 +121,7 @@ function M.load() config.initial_prompt = "You are a coding assistant who receives a project's context and user instructions..." end - -- Merge the default prompt blocks with the config's initial prompt + -- Merge default prompt blocks if type(config.default_prompt_blocks) == "table" and #config.default_prompt_blocks > 0 then local merged_prompt = {} for _, block_name in ipairs(config.default_prompt_blocks) do diff --git a/lua/chatgpt_nvim/init.lua b/lua/chatgpt_nvim/init.lua index 3e51f7e..2ea41c3 100644 --- a/lua/chatgpt_nvim/init.lua +++ b/lua/chatgpt_nvim/init.lua @@ -67,7 +67,6 @@ end -- PROMPT CONSTRUCTION ------------------------------------------------------------------------------ local function build_tools_available_block() - -- We'll list each tool from tools_module.available_tools local lines = {} lines[#lines+1] = "" for _, t in ipairs(tools_module.available_tools) do @@ -94,7 +93,7 @@ local function build_prompt(user_input, dirs, conf) -- 3) local task_lines = {} - table.insert(task_lines, "") + task_lines[#task_lines+1] = "" task_lines[#task_lines+1] = user_input for _, file_path in ipairs(initial_files) do task_lines[#task_lines+1] = ("'%s' (see below for file content)"):format(file_path) @@ -154,7 +153,7 @@ local function handle_step_by_step_if_needed(prompt, conf) end ------------------------------------------------------------------------------ --- :ChatGPT Command +-- :ChatGPT ------------------------------------------------------------------------------ local function run_chatgpt_command() local conf = config.load() @@ -221,7 +220,7 @@ local function run_chatgpt_command() end ------------------------------------------------------------------------------ --- :ChatGPTPaste Command +-- :ChatGPTPaste ------------------------------------------------------------------------------ local function run_chatgpt_paste_command() local conf = config.load() @@ -239,29 +238,21 @@ local function run_chatgpt_paste_command() return end - -- 1) Debug commands - if data.commands and conf.enable_debug_commands then - local results = {} - for _, cmd in ipairs(data.commands) do - table.insert(results, require('chatgpt_nvim').execute_debug_command(cmd, conf)) - end - local output = table.concat(results, "\n\n") - copy_to_clipboard(output) - print("Debug command results copied to clipboard!") - return - end - - -- 2) Tools (handle multiple calls in one request) + -- Check if we have tools if data.tools then + -- Must also verify project name + if not data.project_name or data.project_name ~= conf.project_name then + vim.api.nvim_err_writeln("Project name mismatch or missing. Aborting tool usage.") + return + end + local output_messages = tools_manager.handle_tool_calls(data.tools, conf, is_subpath, read_file) - -- If the output is too large (over conf.prompt_char_limit?), we might respond with a special message. copy_to_clipboard(output_messages) print("Tool call results have been processed and copied to clipboard.") return end - -- 3) If we see project_name & files => final changes or file requests - -- (If the user is still using older YAML style, we handle it, but not recommended.) + -- If we see project_name & files => older YAML style. We handle it but it's discouraged now. if data.project_name and data.files then if data.project_name ~= conf.project_name then vim.api.nvim_err_writeln("Project name mismatch. Aborting.") @@ -294,7 +285,6 @@ local function run_chatgpt_paste_command() vim.api.nvim_err_writeln("Preview not closed in time. Aborting.") return end - end local final_files = data.files @@ -354,7 +344,7 @@ local function run_chatgpt_paste_command() "\n\nProject name: " .. (conf.project_name or ""), "\n\nBelow are the requested files from the project, each preceded by its filename in backticks and enclosed in triple backticks.\n", table.concat(file_sections, "\n"), - "\n\nIf you need more files, please respond again in YAML listing additional files. Or use the new 'tools' array approach to read/edit files. If you have all info, provide final changes or just proceed." + "\n\nIf you need more files, please respond again in YAML listing additional files, or use the 'tools:' approach. If you have all info, provide final changes or continue your instructions." } local prompt = table.concat(sections, "\n") @@ -375,7 +365,7 @@ local function run_chatgpt_paste_command() print("Prompt (with requested files) copied to clipboard! Paste it into ChatGPT.") end else - vim.api.nvim_err_writeln("Invalid response. Expected either 'tools' or 'project_name & files' in YAML.") + vim.api.nvim_err_writeln("No tools or recognized file instructions found. Provide 'tools:' or older 'project_name & files'.") end end @@ -425,94 +415,4 @@ M.run_chatgpt_command = run_chatgpt_command M.run_chatgpt_paste_command = run_chatgpt_paste_command M.run_chatgpt_current_buffer_command = run_chatgpt_current_buffer_command --- Provide debug commands for "ls" and "grep" -M.execute_debug_command = function(cmd, conf) - if type(cmd) ~= "table" or not cmd.command then - return "Invalid command object." - end - local command = cmd.command - if command == "ls" then - local dir = cmd.dir or "." - local args = cmd.args or {} - local cmd_str = "ls" - if #args > 0 then - cmd_str = cmd_str .. " " .. table.concat(args, " ") - end - cmd_str = cmd_str .. " " .. dir - - local handle = io.popen(cmd_str) - if not handle then - return "Failed to run ls command." - end - local result = handle:read("*a") or "" - handle:close() - return "Listing files in: " .. dir .. "\n" .. result - - elseif command == "grep" then - local args = cmd.args or {} - if #args == 0 then - local pattern = cmd.pattern - local target = cmd.target - if not pattern or not target then - return "Usage for grep: {command='grep', args=['-r','pattern','target']} or {pattern='', target=''}" - end - local stat = vim.loop.fs_stat(target) - if not stat then - return "Cannot grep: target path does not exist" - end - - local function do_grep(search_string, filepath) - local c = read_file(filepath) - if not c then - return "Could not read file: " .. filepath - end - local lines = {} - local line_num = 0 - for line in c:gmatch("([^\n]*)\n?") do - line_num = line_num + 1 - if line:find(search_string, 1, true) then - lines[#lines+1] = filepath .. ":" .. line_num .. ":" .. line - end - end - return (#lines == 0) and ("No matches in " .. filepath) or table.concat(lines, "\n") - end - - if stat.type == "directory" then - local h = io.popen("ls -p " .. target .. " | grep -v /") - if not h then - return "Failed to read directory contents for grep." - end - local all_files = {} - for file in h:read("*a"):gmatch("[^\n]+") do - all_files[#all_files+1] = target .. "/" .. file - end - h:close() - local results = {} - for _, f in ipairs(all_files) do - local fstat = vim.loop.fs_stat(f) - if fstat and fstat.type == "file" then - results[#results+1] = do_grep(pattern, f) - end - end - return table.concat(results, "\n") - else - return do_grep(pattern, target) - end - - else - -- new approach with flags/args - local cmd_str = "grep " .. table.concat(args, " ") - local handle = io.popen(cmd_str) - if not handle then - return "Failed to run grep command." - end - local result = handle:read("*a") or "" - handle:close() - return result - end - else - return "Unknown command: " .. command - end -end - return M diff --git a/lua/chatgpt_nvim/prompts.lua b/lua/chatgpt_nvim/prompts.lua index 9e00cd5..8882f1c 100644 --- a/lua/chatgpt_nvim/prompts.lua +++ b/lua/chatgpt_nvim/prompts.lua @@ -44,7 +44,7 @@ local M = { 5. **Styling & CSS Management** - Use your preferred styling approach (CSS Modules, [Tailwind CSS](https://tailwindcss.com/), or standard CSS/SCSS files). - Keep global styles minimal, focusing on utility classes or base styling; keep component-level styles scoped whenever possible. - - If using CSS-in-JS solutions or third-party libraries, ensure they integrate cleanly with Solid’s reactivity. + - If using CSS-in-JS solutions or third-party libraries, ensure they integrate cleanly with Solid’s reactivity. 6. **TypeScript & Linting** - Use **TypeScript** to ensure type safety and improve maintainability. @@ -81,7 +81,7 @@ local M = { 1. **Go Modules** - Use a single `go.mod` file at the project root for module management. - Ensure you use proper import paths based on the module name. - - If you refer to internal packages, use relative paths consistent with the module’s structure (e.g., `moduleName/internal/packageA`). + - If you refer to internal packages, use relative paths consistent with the module’s structure (e.g., `moduleName/internal/packageA`). 2. **Package Structure** - Each folder should contain exactly one package. @@ -110,21 +110,21 @@ local M = { 4. **Coding Best Practices** - Maintain idiomatic Go code (e.g., short function and variable names where obvious, PascalCase for exported symbols). - Keep functions short, focused, and tested. - - Use Go’s standard library where possible before adding third-party dependencies. + - Use Go’s standard library where possible before adding third-party dependencies. - When introducing new functions or types, ensure they are uniquely named to avoid collisions. 5. **Import Management** - Ensure that every import is actually used in your code. - Remove unused imports to keep your code clean and maintainable. - Include all necessary imports for anything referenced in your code to avoid missing imports. - - Verify that any introduced import paths match your module’s structure and do not cause naming conflicts. + - Verify that any introduced import paths match your module’s structure and do not cause naming conflicts. 6. **Output Format** - Present any generated source code as well-organized Go files, respecting the single-package-per-folder rule. - When explaining your reasoning, include any relevant architectural trade-offs and rationale (e.g., “I placed function X in package Y to keep the domain-specific logic separate from the main execution flow.”). - If you modify an existing file, specify precisely which changes or additions you are making. - Please follow these guidelines to ensure the generated or explained code aligns well with Golang’s best practices for large, modular projects. + Please follow these guidelines to ensure the generated or explained code aligns well with Golang’s best practices for large, modular projects. ]], ["typo3-development"] = [[ ### TYPO3 Development Guidelines @@ -150,10 +150,10 @@ local M = { - Keep site configuration in `config/sites/` (for TYPO3 v9+). 2. **Extension Development** - - Create custom functionality as separate extensions (site packages, domain-specific extensions, etc.) following TYPO3’s recommended structure: + - Create custom functionality as separate extensions (site packages, domain-specific extensions, etc.) following TYPO3’s recommended structure: - **Key files**: `ext_emconf.php`, `ext_localconf.php`, `ext_tables.php`, `ext_tables.sql`, `Configuration/`, `Classes/`, `Resources/`. - Use **PSR-4** autoloading and name extensions logically (e.g., `my_sitepackage`, `my_blogextension`). - - Keep your extension’s code under `Classes/` (e.g., Controllers, Models, Services). + - Keep your extension’s code under `Classes/` (e.g., Controllers, Models, Services). - Place Fluid templates, partials, and layouts under `Resources/Private/` (e.g., `Resources/Private/Templates`, `Resources/Private/Partials`, `Resources/Private/Layouts`). 3. **Configuration (TypoScript & TCA)** @@ -186,10 +186,10 @@ local M = { 7. **Output Format** - Present any generated source code or configuration files in a well-organized structure. - Clearly indicate where each file should be placed in the TYPO3 directory layout. - - When explaining your reasoning, include any relevant architectural decisions (e.g., “I created a separate extension for blog functionality to keep it isolated from the site’s main configuration.”). + - When explaining your reasoning, include any relevant architectural decisions (e.g., “I created a separate extension for blog functionality to keep it isolated from the site’s main configuration.”). - If you modify or extend an existing file, specify precisely which changes or additions you are making. - Please follow these guidelines to ensure the generated or explained code aligns well with TYPO3’s best practices for large, maintainable projects. + Please follow these guidelines to ensure the generated or explained code aligns well with TYPO3’s best practices for large, maintainable projects. ]], ["rust-development"] = [[ ### Rust Development Guidelines @@ -204,11 +204,11 @@ local M = { 2. **Crates & Packages** - Split the application into logical crates (libraries and/or binaries). - Each crate should have a single main **library** (`lib.rs`) or **binary** (`main.rs`) in its `src/` folder. - - Name crates, modules, and files clearly, following Rust’s naming conventions (e.g., `snake_case` for files/modules, `PascalCase` for types). + - Name crates, modules, and files clearly, following Rust’s naming conventions (e.g., `snake_case` for files/modules, `PascalCase` for types). - Avoid duplicating the same function or type in multiple crates; share common functionality via a dedicated library crate if needed. 3. **Folder & Module Structure** - - Organize code within each crate using Rust’s module system, keeping related functions and types in logical modules/submodules. + - Organize code within each crate using Rust’s module system, keeping related functions and types in logical modules/submodules. - A typical directory layout for a workspace with multiple crates might look like: ``` myproject/ @@ -226,7 +226,7 @@ local M = { ├── target/ └── ... ``` - - If you have integration tests, store them in a `tests/` folder at the crate root, or use the workspace root’s `tests/` directory if they span multiple crates. + - If you have integration tests, store them in a `tests/` folder at the crate root, or use the workspace root’s `tests/` directory if they span multiple crates. 4. **Coding & Documentation Best Practices** - Write **idiomatic Rust** code: @@ -256,54 +256,40 @@ local M = { ["basic-prompt"] = [[ ### Basic Prompt - 1. **Analyze Required Files** - - Check the project structure (or any provided file list). - - If you identify a file reference or function you do not have, **produce a YAML snippet** requesting that file’s content in full. For example: - ```yaml - project_name: my_project - files: - - path: "relative/path/to/needed_file" - ``` - - Do not proceed with final changes if you lack the necessary file contents. - - Always provide the entire file content when you do include a file. + You are assisting me in a coding workflow for a project (e.g., "my_project"). Whenever you need to inspect or modify files, or execute commands, you must provide both: - 2. **Request File Contents** - - For every file you need but don’t have, provide a short YAML snippet (like above) to retrieve it. - - Ask any clarifying questions outside the YAML code block. + 1. `project_name: ""` (matching the real project name) + 2. A `tools:` array describing the operations you want to perform. - 3. **Provide Output YAML** - - When you have all information, output the final YAML in this format: - ```yaml - project_name: my_project + **Example** (substitute `` with the real name): + ```yaml + project_name: "my_project" + tools: + - tool: "readFile" + path: "relative/path/to/file" - files: - - path: "src/main.py" - content: | - # Updated main function - def main(): - print("Hello, World!") + - tool: "replace_in_file" + path: "relative/path/to/file" + replacements: + - search: "old text" + replace: "new text" - - path: "docs/README.md" - delete: true - ``` - - `project_name` must match the project’s configured name. - - Each file item in `files` must have `path` and either `content` or `delete: true`. + - tool: "editFile" + path: "relative/path/to/file" + content: | + # Full updated file content here - 4. **Explain Changes** - - After the final YAML, add a brief explanation of the modifications (outside the YAML). + - tool: "executeCommand" + command: "ls -la" + ``` - 5. **Iterate as Needed** - - If further context or changes are required, repeat steps to request files or clarifications. - - 6. **Important Notes** - - Never modify or delete a file you haven’t explicitly requested or received. - - Use comments in code only to clarify code logic, not to explain your thought process. - - Explanations go outside the YAML. - - 7. **Best Practices** - - Keep file paths accurate to avoid mistakes during implementation. - - Maintain a clear, logical structure for your changes. - - Use the final YAML consistently for clarity. + **Key Points**: + - Always include `project_name: ""` in the same YAML as `tools`. + - If you only need one tool, include just one object in the `tools` array. + - If multiple tools are needed, list them sequentially under `tools`. + - Allways run at least one tool (e.g., `readFile`, `editFile`, `executeCommand`), exept you have finished. + - The plugin will verify the `project_name` is correct before running any tools. + - If the response grows too large, I'll guide you to break it into smaller steps. ]], ["secure-coding"] = [[ ### Secure Coding Guidelines @@ -370,33 +356,10 @@ local M = { ["step-prompt"] = [[ It appears this request might exceed the model's prompt character limit if done all at once. Please break down the tasks into smaller steps and handle them one by one. - At each step, we'll provide relevant files or context if needed. Thank you! ]], ["file-selection-instructions"] = [[ Delete lines for directories you do NOT want, then save & close (e.g. :wq, :x, or :bd) - ]], - ["debug-commands-info"] = [[ - ### Debugging Commands - - If you need debugging commands, include them in your YAML response as follows: - - ```yaml - commands: - - command: "ls" - args: ["-l", "path/to/directory"] - - - command: "grep" - args: ["-r", "searchString", "path/to/file/or/directory"] - ``` - - The "ls" command uses the system's 'ls' command to list directory contents. You can pass flags or additional arguments in `args`. - The "grep" command searches for a given pattern in files or directories, again receiving flags or additional arguments in `args`. - If you omit `args` for grep, you can still use the older format with `pattern` and `target` for backward compatibility. - - This are the only two commands supported at the moment. - - When these commands are present and `enable_debug_commands` is true, I'll execute them and return the results in the clipboard. ]] } diff --git a/lua/chatgpt_nvim/tools/manager.lua b/lua/chatgpt_nvim/tools/manager.lua index 49c1674..29af448 100644 --- a/lua/chatgpt_nvim/tools/manager.lua +++ b/lua/chatgpt_nvim/tools/manager.lua @@ -1,8 +1,7 @@ local tools_module = require("chatgpt_nvim.tools") -local uv = vim.loop + local M = {} --- Simple destructive command check local function is_destructive_command(cmd) if not cmd then return false end local destructive_list = { "rm", "sudo", "mv", "cp" } @@ -14,7 +13,6 @@ local function is_destructive_command(cmd) return false end --- Prompt user if not auto-accepted or if command is destructive local function prompt_user_tool_accept(tool_call, conf) local function ask_user(msg) vim.api.nvim_out_write(msg .. " [y/N]: ") @@ -39,11 +37,9 @@ local function prompt_user_tool_accept(tool_call, conf) end end --- We'll pass references to `read_file` from init. local function handle_tool_calls(tools, conf, is_subpath_fn, read_file_fn) local messages = {} for _, call in ipairs(tools) do - -- Prompt user acceptance local accepted = prompt_user_tool_accept(call, conf) if not accepted then table.insert(messages, string.format("Tool [%s] was rejected by user.", call.tool or "nil")) @@ -58,7 +54,12 @@ local function handle_tool_calls(tools, conf, is_subpath_fn, read_file_fn) end end - return table.concat(messages, "\n\n") + local combined = table.concat(messages, "\n\n") + local limit = conf.prompt_char_limit or 8000 + if #combined > limit then + return "The combined tool output is too large ("..#combined.."). Please break down the operations into smaller steps." + end + return combined end M.handle_tool_calls = handle_tool_calls