Compare commits
12 Commits
change-to-
...
4d679e43cb
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d679e43cb | |||
| d0712ef8a5 | |||
| 6723b802bf | |||
| aa12bca3ab | |||
| e032be0118 | |||
| deaec8094b | |||
| a27e3da769 | |||
| 84e71dcbef | |||
| 50ba937ae0 | |||
| d5b05ede36 | |||
| 5dc568e9e5 | |||
| e9fd204977 |
97
README.md
97
README.md
@@ -4,28 +4,36 @@
|
|||||||
This plugin integrates a ChatGPT O1 model workflow into Neovim. It allows you to:
|
This plugin integrates a ChatGPT O1 model workflow into Neovim. It allows you to:
|
||||||
|
|
||||||
1. Generate prompts containing:
|
1. Generate prompts containing:
|
||||||
- An **initial prompt** (from `.chatgpt_config.yaml`)
|
- An **initial prompt** (loaded from `.chatgpt_config.yaml` or `chatgpt_config.yaml`)
|
||||||
- A list of directories (also specified in `.chatgpt_config.yaml`) from which it gathers the project structure and file contents
|
- A list of directories (as specified in your configuration) from which it gathers the project structure
|
||||||
- **Interactive file selection** if enabled, so you can pick exactly which directories to include
|
- **Interactive file selection** (if enabled) so you can choose which directories to include
|
||||||
- Any **initial files** you define (e.g., `README.md`, etc.)
|
- Any **initial files** you define (e.g. `README.md`, etc.)
|
||||||
|
- Optionally, **file contents** can be appended to the prompt when the new `include_file_contents` flag is enabled
|
||||||
|
|
||||||
2. Copy these prompts to your clipboard to paste into ChatGPT O1.
|
2. Copy these prompts to your clipboard to paste into ChatGPT O1.
|
||||||
3. Receive YAML changes from ChatGPT, then run `:ChatGPTPaste` to apply them or supply additional files.
|
3. Receive YAML changes from ChatGPT and run `:ChatGPTPaste` to apply them or to supply additional files.
|
||||||
|
|
||||||
## New Key Features
|
## New Key Features
|
||||||
|
|
||||||
- **Step-by-Step Prompting** (`enable_step_by_step: true`):
|
- **Step-by-Step Prompting** (`enable_step_by_step: true`):
|
||||||
If the request grows too large (exceeds `token_limit`), the plugin automatically generates a special prompt asking the model to split the task into smaller steps, working through them one by one. This approach helps you stay within the model’s maximum token limit without having to manually break things apart.
|
If the generated prompt grows too large (exceeding the configured `prompt_char_limit`), the plugin automatically produces a special prompt asking the model to split the task into smaller steps. This ensures you remain within the model’s maximum token capacity without manually breaking your task apart.
|
||||||
|
|
||||||
- **Partial Acceptance**: If `partial_acceptance: true`, you can open a buffer that lists the final changes. Remove or comment out lines you don’t want, then only those changes are applied.
|
- **Partial Acceptance**:
|
||||||
|
If `partial_acceptance: true`, you can open a buffer displaying the proposed changes. Remove or comment out lines you do not wish to apply, and only the remaining changes will be executed.
|
||||||
|
|
||||||
- **Preview Changes**: If `preview_changes: true`, you get a buffer showing proposed changes before you apply them.
|
- **Preview Changes**:
|
||||||
|
If `preview_changes: true`, a buffer will show the proposed changes before they are applied.
|
||||||
|
|
||||||
- **Interactive File Selection**: If `interactive_file_selection: true`, you choose which directories from `.chatgpt_config.yaml` get included in the prompt, reducing token usage.
|
- **Interactive File Selection**:
|
||||||
|
When `interactive_file_selection: true`, you can choose which directories (from your config file) to include in the prompt, thus managing token usage more effectively.
|
||||||
|
|
||||||
- **Improved Debug**: If `improved_debug: true`, debug logs go into a dedicated `ChatGPT_Debug_Log` buffer for easier reading.
|
- **Include File Contents**:
|
||||||
|
A new configuration flag `include_file_contents` (default: `false`) lets you include the entire contents of the project files in the prompt. When enabled, the plugin gathers and appends the file contents from the selected directories. It counts the total characters and, if the combined file contents exceed the `prompt_char_limit`, it notifies you to disable this feature to avoid exceeding the model’s capacity.
|
||||||
|
|
||||||
## Example `.chatgpt_config.yaml`
|
- **Improved Debug**:
|
||||||
|
If `improved_debug: true`, detailed debug logs are sent to a dedicated `ChatGPT_Debug_Log` buffer for easier review.
|
||||||
|
|
||||||
|
## Example `.chatgpt_config.yaml` or `chatgpt_config.yaml`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
project_name: "chatgpt_nvim"
|
project_name: "chatgpt_nvim"
|
||||||
@@ -37,40 +45,43 @@ directories:
|
|||||||
initial_files:
|
initial_files:
|
||||||
- "README.md"
|
- "README.md"
|
||||||
debug: false
|
debug: false
|
||||||
|
include_file_contents: false # Set to true to include file contents in the prompt
|
||||||
enable_step_by_step: true
|
enable_step_by_step: true
|
||||||
preview_changes: true
|
preview_changes: true
|
||||||
interactive_file_selection: true
|
interactive_file_selection: true
|
||||||
partial_acceptance: true
|
partial_acceptance: true
|
||||||
improved_debug: true
|
improved_debug: true
|
||||||
token_limit: 3000
|
prompt_char_limit: 300000 # Maximum characters allowed in the prompt
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. **`:ChatGPT`**
|
1. **`:ChatGPT`**
|
||||||
- If `interactive_file_selection` is on, you’ll pick directories to include in a buffer named `ChatGPT_File_Selection`.
|
- If `interactive_file_selection` is enabled, you will select directories via a buffer (named `ChatGPT_File_Selection`).
|
||||||
- Save & close with `:wq`, `:x`, or `:bd` (you don’t have to use `:q`).
|
- Save and close the buffer with `:wq`, `:x`, or `:bd` (no need to use `:q`).
|
||||||
- If `enable_step_by_step` is on and the prompt might exceed `token_limit`, the plugin will generate instructions prompting the model to address each step separately.
|
- If `enable_step_by_step` is active and the prompt might exceed `prompt_char_limit`, the plugin will generate a step-by-step instruction prompt.
|
||||||
|
|
||||||
2. **Paste Prompt to ChatGPT**
|
2. **Paste Prompt to ChatGPT**
|
||||||
- If the task is split into steps, simply copy/paste them one by one into ChatGPT.
|
- If the task is split into steps, copy and paste them one by one into ChatGPT.
|
||||||
|
|
||||||
3. **`:ChatGPTPaste`**
|
3. **`:ChatGPTPaste`**
|
||||||
- The plugin reads the YAML from your clipboard. If it requests more files, it might again suggest a step-by-step approach.
|
- The plugin reads the YAML response from your clipboard. If additional files are requested, a step-by-step approach might be suggested.
|
||||||
- If final changes are provided:
|
- When final changes are provided:
|
||||||
- Optionally preview them (`preview_changes`).
|
- You can optionally preview them (`preview_changes`).
|
||||||
- Optionally partially accept them (`partial_acceptance`).
|
- You can optionally partially accept them (`partial_acceptance`).
|
||||||
- Then the plugin writes/deletes files as specified.
|
- Finally, the plugin applies file writes or deletions as specified in the YAML.
|
||||||
|
|
||||||
## Troubleshooting & Tips
|
## Troubleshooting & Tips
|
||||||
- Adjust `token_limit` in `.chatgpt_config.yaml` as needed.
|
|
||||||
- If partial acceptance is confusing, remember to remove or prepend `#` to lines you don’t want before saving and closing the buffer.
|
- Adjust `prompt_char_limit` in your configuration file as needed.
|
||||||
- If step-by-step prompting occurs, ensure you follow each prompt the model provides in the correct order.
|
- If partial acceptance is confusing, simply remove or comment out lines you do not want before finalizing the buffer.
|
||||||
- Check `ChatGPT_Debug_Log` if `improved_debug` is on, or the Neovim messages if `debug` is on, for detailed info.
|
- When step-by-step prompting occurs, ensure you follow each instruction in the correct order.
|
||||||
- You can close the selection or prompt buffers at any time with commands like `:bd`, `:x`, or `:wq`. No need to rely on `:q`.
|
- For detailed debug information, check the `ChatGPT_Debug_Log` buffer (if `improved_debug` is enabled) or the standard Neovim messages (if `debug` is enabled).
|
||||||
|
- You can close the selection or prompt buffers at any time using commands like `:bd`, `:x`, or `:wq`.
|
||||||
|
|
||||||
## Debug Commands
|
## Debug Commands
|
||||||
If `enable_debug_commands` is true, you can include commands like these in your YAML:
|
|
||||||
|
If `enable_debug_commands` is true, you can include commands such as:
|
||||||
```yaml
|
```yaml
|
||||||
commands:
|
commands:
|
||||||
- command: "list"
|
- command: "list"
|
||||||
@@ -80,21 +91,23 @@ commands:
|
|||||||
pattern: "searchString"
|
pattern: "searchString"
|
||||||
target: "path/to/file/or/directory"
|
target: "path/to/file/or/directory"
|
||||||
```
|
```
|
||||||
The **list** command now uses the Linux `ls` command to list directory contents. The **grep** command searches for a given pattern in a file or all files in a directory.
|
The **list** command uses the Linux `ls` command to list directory contents, while the **grep** command searches for a given pattern within files or directories.
|
||||||
|
|
||||||
Enjoy your improved, more flexible ChatGPT Neovim plugin with step-by-step support!
|
Enjoy your improved, flexible ChatGPT NeoVim plugin with step-by-step support and enhanced file inclusion capabilities!
|
||||||
|
|
||||||
## Test when developing
|
## Test when Developing
|
||||||
add new plugin to runtimepath
|
|
||||||
```vim
|
|
||||||
:set rtp^=~/temp_plugins/chatgpt.vim
|
|
||||||
```
|
|
||||||
Clear Lua module cache
|
|
||||||
```vim
|
|
||||||
:lua for k in pairs(package.loaded) do if k:match("^chatgpt_nvim") then package.loaded[k]=nil end end
|
|
||||||
```
|
|
||||||
Load new plugin code
|
|
||||||
```vim
|
|
||||||
:runtime plugin/chatgpt.vim
|
|
||||||
```
|
|
||||||
|
|
||||||
|
- **Add plugin to runtimepath:**
|
||||||
|
```vim
|
||||||
|
:set rtp^=~/temp_plugins/chatgpt.vim
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Clear Lua module cache:**
|
||||||
|
```vim
|
||||||
|
:lua for k in pairs(package.loaded) do if k:match("^chatgpt_nvim") then package.loaded[k]=nil end end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Load new plugin code:**
|
||||||
|
```vim
|
||||||
|
:runtime plugin/chatgpt.vim
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
project_name: "chatgpt_nvim"
|
project_name: "chatgpt.vim"
|
||||||
default_prompt_blocks:
|
default_prompt_blocks:
|
||||||
- "basic-prompt"
|
- "basic-prompt"
|
||||||
- "secure-coding"
|
- "secure-coding"
|
||||||
initial_files:
|
|
||||||
- "README.md"
|
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
improved_debug: false
|
improved_debug: false
|
||||||
@@ -20,8 +18,9 @@ auto_lint: true
|
|||||||
|
|
||||||
# New tool auto-accept config
|
# New tool auto-accept config
|
||||||
tool_auto_accept:
|
tool_auto_accept:
|
||||||
readFile: false
|
readFile: true
|
||||||
editFile: false
|
editFile: true
|
||||||
|
replace_in_file: true
|
||||||
executeCommand: false
|
executeCommand: false
|
||||||
# If you set any of these to true, it will auto accept them without prompting.
|
# If you set any of these to true, it will auto accept them without prompting.
|
||||||
# 'executeCommand' should remain false by default unless you're certain it's safe.
|
# 'executeCommand' should remain false by default unless you're certain it's safe.
|
||||||
@@ -4,6 +4,7 @@ local uv = vim.loop
|
|||||||
local ok_yaml, lyaml = pcall(require, "lyaml")
|
local ok_yaml, lyaml = pcall(require, "lyaml")
|
||||||
local prompts = require("chatgpt_nvim.prompts")
|
local prompts = require("chatgpt_nvim.prompts")
|
||||||
|
|
||||||
|
-- Determines the Git root or fallback directory.
|
||||||
local function get_project_root()
|
local function get_project_root()
|
||||||
local current_file = vim.fn.expand("%:p")
|
local current_file = vim.fn.expand("%:p")
|
||||||
local root_dir
|
local root_dir
|
||||||
@@ -28,14 +29,30 @@ local function get_project_root()
|
|||||||
return root_dir
|
return root_dir
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Attempt to locate either .chatgpt_config.yaml or chatgpt_config.yaml, returning whichever exists first.
|
||||||
local function get_config_path()
|
local function get_config_path()
|
||||||
local root = get_project_root()
|
local root = get_project_root()
|
||||||
return root .. "/.chatgpt_config.yaml"
|
local dot_config = root .. "/.chatgpt_config.yaml"
|
||||||
|
local non_dot_config = root .. "/chatgpt_config.yaml"
|
||||||
|
|
||||||
|
local dot_fd = uv.fs_open(dot_config, "r", 438)
|
||||||
|
if dot_fd then
|
||||||
|
uv.fs_close(dot_fd)
|
||||||
|
return dot_config
|
||||||
|
end
|
||||||
|
|
||||||
|
local non_dot_fd = uv.fs_open(non_dot_config, "r", 438)
|
||||||
|
if non_dot_fd then
|
||||||
|
uv.fs_close(non_dot_fd)
|
||||||
|
return non_dot_config
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.load()
|
function M.load()
|
||||||
|
-- Attempt to find either config file, else fallback
|
||||||
local path = get_config_path()
|
local path = get_config_path()
|
||||||
local fd = uv.fs_open(path, "r", 438)
|
|
||||||
local config = {
|
local config = {
|
||||||
initial_prompt = "",
|
initial_prompt = "",
|
||||||
directories = { "." },
|
directories = { "." },
|
||||||
@@ -44,6 +61,7 @@ function M.load()
|
|||||||
project_name = "",
|
project_name = "",
|
||||||
debug = false,
|
debug = false,
|
||||||
initial_files = {},
|
initial_files = {},
|
||||||
|
include_file_contents = false, -- NEW FLAG: include file contents in prompt if true
|
||||||
preview_changes = false,
|
preview_changes = false,
|
||||||
interactive_file_selection = false,
|
interactive_file_selection = false,
|
||||||
partial_acceptance = false,
|
partial_acceptance = false,
|
||||||
@@ -62,10 +80,22 @@ function M.load()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- If no config file found, alert user and return default config
|
||||||
|
if not path then
|
||||||
|
config.initial_prompt = "You are a coding assistant who receives a project's context and user instructions..."
|
||||||
|
vim.notify(
|
||||||
|
"No config file found (tried .chatgpt_config.yaml, chatgpt_config.yaml). Using defaults.",
|
||||||
|
vim.log.levels.WARN
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
end
|
||||||
|
|
||||||
|
local fd = uv.fs_open(path, "r", 438)
|
||||||
if fd then
|
if fd then
|
||||||
local stat = uv.fs_fstat(fd)
|
local stat = uv.fs_fstat(fd)
|
||||||
local data = uv.fs_read(fd, stat.size, 0)
|
local data = uv.fs_read(fd, stat.size, 0)
|
||||||
uv.fs_close(fd)
|
uv.fs_close(fd)
|
||||||
|
|
||||||
if data and ok_yaml then
|
if data and ok_yaml then
|
||||||
local ok, result = pcall(lyaml.load, data)
|
local ok, result = pcall(lyaml.load, data)
|
||||||
if ok and type(result) == "table" then
|
if ok and type(result) == "table" then
|
||||||
@@ -90,6 +120,9 @@ function M.load()
|
|||||||
if type(result.initial_files) == "table" then
|
if type(result.initial_files) == "table" then
|
||||||
config.initial_files = result.initial_files
|
config.initial_files = result.initial_files
|
||||||
end
|
end
|
||||||
|
if type(result.include_file_contents) == "boolean" then -- LOAD NEW FLAG
|
||||||
|
config.include_file_contents = result.include_file_contents
|
||||||
|
end
|
||||||
if type(result.preview_changes) == "boolean" then
|
if type(result.preview_changes) == "boolean" then
|
||||||
config.preview_changes = result.preview_changes
|
config.preview_changes = result.preview_changes
|
||||||
end
|
end
|
||||||
@@ -109,7 +142,6 @@ function M.load()
|
|||||||
config.enable_step_by_step = result.enable_step_by_step
|
config.enable_step_by_step = result.enable_step_by_step
|
||||||
end
|
end
|
||||||
|
|
||||||
-- auto_lint controls whether we do LSP-based checks
|
|
||||||
if type(result.auto_lint) == "boolean" then
|
if type(result.auto_lint) == "boolean" then
|
||||||
config.auto_lint = result.auto_lint
|
config.auto_lint = result.auto_lint
|
||||||
end
|
end
|
||||||
@@ -145,6 +177,12 @@ function M.load()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Include project name in the final initial prompt, if set
|
||||||
|
if config.project_name ~= "" then
|
||||||
|
config.initial_prompt =
|
||||||
|
"[Project Name: " .. config.project_name .. "]\n\n" .. config.initial_prompt
|
||||||
|
end
|
||||||
|
|
||||||
if config.debug then
|
if config.debug then
|
||||||
vim.api.nvim_out_write("[chatgpt_nvim:config] Loaded config from: " .. path .. "\n")
|
vim.api.nvim_out_write("[chatgpt_nvim:config] Loaded config from: " .. path .. "\n")
|
||||||
vim.api.nvim_out_write("[chatgpt_nvim:config] Debug logging is enabled.\n")
|
vim.api.nvim_out_write("[chatgpt_nvim:config] Debug logging is enabled.\n")
|
||||||
|
|||||||
@@ -185,4 +185,22 @@ function M.get_file_contents(files, conf)
|
|||||||
return table.concat(sections, "\n")
|
return table.concat(sections, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
-- NEW FUNCTION: Build the project prompt by optionally including file contents.
|
||||||
|
function M.get_project_prompt(directories, conf)
|
||||||
|
local structure = M.get_project_structure(directories, conf)
|
||||||
|
if conf.include_file_contents then
|
||||||
|
local files = M.get_project_files(directories, conf)
|
||||||
|
local contents = M.get_file_contents(files, conf)
|
||||||
|
local total_chars = #contents
|
||||||
|
if total_chars > conf.prompt_char_limit then
|
||||||
|
vim.notify("Total file contents (" .. total_chars .. " characters) exceed the prompt limit (" .. conf.prompt_char_limit .. "). Please disable 'include_file_contents' in your config.", vim.log.levels.ERROR)
|
||||||
|
return structure
|
||||||
|
else
|
||||||
|
return structure .. "\n" .. contents
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return structure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ local function build_prompt(user_input, dirs, conf)
|
|||||||
env_lines[#env_lines+1] = os.date("%x, %X (%Z)")
|
env_lines[#env_lines+1] = os.date("%x, %X (%Z)")
|
||||||
env_lines[#env_lines+1] = ""
|
env_lines[#env_lines+1] = ""
|
||||||
env_lines[#env_lines+1] = "# Current Working Directory (" .. root .. ") Files"
|
env_lines[#env_lines+1] = "# Current Working Directory (" .. root .. ") Files"
|
||||||
env_lines[#env_lines+1] = context.get_project_structure(dirs, conf) or ""
|
-- Using the new get_project_prompt function instead of get_project_structure.
|
||||||
|
env_lines[#env_lines+1] = context.get_project_prompt(dirs, conf) or ""
|
||||||
env_lines[#env_lines+1] = ""
|
env_lines[#env_lines+1] = ""
|
||||||
env_lines[#env_lines+1] = "# Current Mode"
|
env_lines[#env_lines+1] = "# Current Mode"
|
||||||
env_lines[#env_lines+1] = "ACT MODE"
|
env_lines[#env_lines+1] = "ACT MODE"
|
||||||
|
|||||||
@@ -73,6 +73,95 @@ local M = {
|
|||||||
|
|
||||||
Please follow these guidelines to ensure the generated or explained code aligns well with SolidJS best practices for large, maintainable projects.
|
Please follow these guidelines to ensure the generated or explained code aligns well with SolidJS best practices for large, maintainable projects.
|
||||||
]],
|
]],
|
||||||
|
["nodejs-development"] = [[
|
||||||
|
You are helping me develop a large Node.js application. Please keep the following points in mind when generating or explaining code:
|
||||||
|
|
||||||
|
1. **Project & Folder Structure**
|
||||||
|
- Maintain a clear, top-level directory layout. For example:
|
||||||
|
```
|
||||||
|
my-node-app/
|
||||||
|
├── src/
|
||||||
|
│ ├── controllers/
|
||||||
|
│ ├── models/
|
||||||
|
│ ├── routes/
|
||||||
|
│ ├── services/
|
||||||
|
│ ├── utils/
|
||||||
|
│ └── index.js (or app.js)
|
||||||
|
├── tests/
|
||||||
|
│ └── ...
|
||||||
|
├── package.json
|
||||||
|
├── .env (if needed)
|
||||||
|
├── .eslintrc.js
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
- Keep your application logic separated in folders:
|
||||||
|
- **controllers/** (or handlers) for request handling logic,
|
||||||
|
- **services/** for core business logic or data processing,
|
||||||
|
- **models/** for database schemas/entities,
|
||||||
|
- **routes/** for defining routes/endpoints,
|
||||||
|
- **utils/** for reusable helper functions.
|
||||||
|
|
||||||
|
2. **Dependencies & Package Management**
|
||||||
|
- Use **npm** or **yarn** (pick one and stay consistent) to manage dependencies.
|
||||||
|
- Keep your `package.json` clean by removing unused dependencies.
|
||||||
|
- Pin exact versions for critical or sensitive dependencies to ensure reproducible builds.
|
||||||
|
|
||||||
|
3. **Configuration & Environment Variables**
|
||||||
|
- Use environment variables to store sensitive or environment-specific information (e.g., database credentials, API keys).
|
||||||
|
- Consider a config management library (like [dotenv](https://github.com/motdotla/dotenv) or [dotenv-flow](https://github.com/kerimdzhanov/dotenv-flow)) to load environment variables from `.env` files.
|
||||||
|
- Keep secrets out of version control (e.g., `.env` should be in `.gitignore`).
|
||||||
|
|
||||||
|
4. **Code Organization & Module Patterns**
|
||||||
|
- Use **ES Modules** (`import`/`export`) or **CommonJS** (`require`/`module.exports`) consistently across the project.
|
||||||
|
- Keep each module focused on a single responsibility.
|
||||||
|
- Use a **service layer** for business logic to avoid bloated controllers.
|
||||||
|
- Use **async/await** for asynchronous operations, ensuring proper error handling (try/catch blocks or .catch callbacks).
|
||||||
|
|
||||||
|
5. **Database & Data Persistence**
|
||||||
|
- Use an ORM/ODM (e.g., [Sequelize](https://sequelize.org/) for SQL, [Mongoose](https://mongoosejs.com/) for MongoDB) or a query builder ([Knex.js](https://knexjs.org/)) to maintain cleaner database interactions.
|
||||||
|
- Keep database-related logic in separate **models/** or **repositories/**.
|
||||||
|
- Handle database migrations carefully (e.g., with [db-migrate](https://db-migrate.readthedocs.io/), [Liquibase](https://www.liquibase.org/), or built-in ORM migration tools).
|
||||||
|
|
||||||
|
6. **Logging & Monitoring**
|
||||||
|
- Use a structured logging library (e.g., [Winston](https://github.com/winstonjs/winston), [Pino](https://github.com/pinojs/pino)) to capture logs in JSON or another parseable format.
|
||||||
|
- Ensure logs include enough context (request IDs, timestamps, etc.) to troubleshoot issues.
|
||||||
|
- Consider external logging and monitoring solutions (e.g., [Datadog](https://www.datadoghq.com/), [New Relic](https://newrelic.com/)) for production environments.
|
||||||
|
|
||||||
|
7. **Security & Best Practices**
|
||||||
|
- Sanitize and validate all user inputs (e.g., using libraries like [validator](https://github.com/validatorjs/validator.js)).
|
||||||
|
- Avoid SQL injection by using parameterized queries or ORM features.
|
||||||
|
- Implement rate limiting or request throttling to prevent abuse (e.g., [express-rate-limit](https://github.com/nfriedly/express-rate-limit)).
|
||||||
|
- Ensure **HTTPS** is used in production and secure headers are set (e.g., [helmet](https://github.com/helmetjs/helmet) for Express).
|
||||||
|
- Keep dependencies updated to patch known vulnerabilities (use `npm audit` or equivalent).
|
||||||
|
|
||||||
|
8. **Error Handling**
|
||||||
|
- Use centralized error handling middleware (if using a framework like Express) to catch and process errors consistently.
|
||||||
|
- Provide clear error messages but avoid leaking sensitive info in production.
|
||||||
|
- Separate operational errors (e.g., user-related) from programmer errors (e.g., logic bugs) to handle them appropriately.
|
||||||
|
|
||||||
|
9. **Testing & Quality Assurance**
|
||||||
|
- Write **unit tests** for individual modules or functions (e.g., using [Jest](https://jestjs.io/), [Mocha](https://mochajs.org/)).
|
||||||
|
- Use **integration tests** or **end-to-end tests** (e.g., [Supertest](https://github.com/visionmedia/supertest) for API endpoints).
|
||||||
|
- Aim for high coverage but focus on critical business logic and error cases.
|
||||||
|
- Automate tests in your CI pipeline.
|
||||||
|
|
||||||
|
10. **Linting & Formatting**
|
||||||
|
- Use **ESLint** (with recommended or popular config like [Airbnb](https://www.npmjs.com/package/eslint-config-airbnb)) for consistent code quality.
|
||||||
|
- Use **Prettier** for code formatting to maintain a standardized style.
|
||||||
|
- Configure linting and formatting checks in a pre-commit hook or CI (e.g., [Husky](https://typicode.github.io/husky/), [lint-staged](https://github.com/okonet/lint-staged)).
|
||||||
|
|
||||||
|
11. **Deployment & Environment Management**
|
||||||
|
- Containerize your app with Docker if possible, specifying a secure and minimal base image.
|
||||||
|
- Use process managers like [PM2](https://pm2.keymetrics.io/) or systemd for production Node.js processes.
|
||||||
|
- Maintain separate configuration (or environment variables) for staging, production, etc.
|
||||||
|
|
||||||
|
12. **Output Format**
|
||||||
|
- Present any generated source code with clear folder and file placement (e.g., `controllers/`, `services/`).
|
||||||
|
- When explaining your reasoning, highlight the architectural decisions or patterns used (e.g., “I introduced a service layer to separate business logic from route handling.”).
|
||||||
|
- If you modify existing files, specify precisely which lines or sections have changed, and why.
|
||||||
|
|
||||||
|
Please follow these guidelines to ensure the generated or explained code aligns well with Node.js best practices for large, maintainable projects.
|
||||||
|
]]
|
||||||
["go-development"] = [[
|
["go-development"] = [[
|
||||||
### Go Development Guidelines
|
### Go Development Guidelines
|
||||||
|
|
||||||
@@ -289,8 +378,62 @@ local M = {
|
|||||||
- If multiple tools are needed, list them sequentially in the `tools` array.
|
- If multiple tools are needed, list them sequentially in the `tools` array.
|
||||||
- Always run at least one tool (e.g., `readFile`, `editFile`, `executeCommand`), exept you have finished.
|
- Always run at least one tool (e.g., `readFile`, `editFile`, `executeCommand`), exept you have finished.
|
||||||
- Always just include one yaml in the response with all the tools you want to run in that yaml.
|
- Always just include one yaml in the response with all the tools you want to run in that yaml.
|
||||||
|
- Never do write operations on a file which you have not read before. Its contents must be in your context before writing. This does not apply if you create a new file.
|
||||||
- The plugin will verify the `project_name` is correct before running any tools.
|
- 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.
|
- If the response grows too large, I'll guide you to break it into smaller steps.
|
||||||
|
|
||||||
|
You are assisting me in a coding workflow for a project (e.g., "my_project").
|
||||||
|
|
||||||
|
1. **Gather Context / Ask Questions**
|
||||||
|
- If you need more information or something is unclear, **ask me directly** in plain text, without calling any tools.
|
||||||
|
|
||||||
|
2. **Inspect Files**
|
||||||
|
- When you need to check a file’s content, use:
|
||||||
|
```yaml
|
||||||
|
project_name: "my_project"
|
||||||
|
tools:
|
||||||
|
- tool: "readFile"
|
||||||
|
path: "relative/path/to/file"
|
||||||
|
```
|
||||||
|
- Read the file before deciding on any modifications.
|
||||||
|
|
||||||
|
3. **Make Changes**
|
||||||
|
- If you need to modify an existing file (after reading it), use:
|
||||||
|
```yaml
|
||||||
|
project_name: "my_project"
|
||||||
|
tools:
|
||||||
|
- tool: "editFile"
|
||||||
|
path: "relative/path/to/file"
|
||||||
|
content: |
|
||||||
|
# Full updated file content
|
||||||
|
```
|
||||||
|
- Or perform incremental text replacements with:
|
||||||
|
```yaml
|
||||||
|
project_name: "my_project"
|
||||||
|
tools:
|
||||||
|
- tool: "replace_in_file"
|
||||||
|
path: "relative/path/to/file"
|
||||||
|
replacements:
|
||||||
|
- search: "old text"
|
||||||
|
replace: "new text"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Run Commands (Optional)**
|
||||||
|
- To run tests, list files, or do other checks, use:
|
||||||
|
```yaml
|
||||||
|
project_name: "my_project"
|
||||||
|
tools:
|
||||||
|
- tool: "executeCommand"
|
||||||
|
command: "shell command here"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Important Rules**
|
||||||
|
- Always start with 1 or 2, but afterwards you can mix 1, 2, 3, and 4 as needed.
|
||||||
|
- Include `project_name: "my_project"` whenever you call `tools`.
|
||||||
|
- Keep each tool call in the `tools` array (multiple if needed).
|
||||||
|
- **Never write to a file you haven’t read** in this session and already got the content from an response (unless creating a new file).
|
||||||
|
- Follow secure coding guidelines (input validation, least privilege, no sensitive info in logs, etc.).
|
||||||
|
- When done, provide a final answer **without** calling any tools.
|
||||||
]],
|
]],
|
||||||
["secure-coding"] = [[
|
["secure-coding"] = [[
|
||||||
### Secure Coding Guidelines
|
### Secure Coding Guidelines
|
||||||
|
|||||||
@@ -1,17 +1,36 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
||||||
|
-- Validate the command exists
|
||||||
local cmd = tool_call.command
|
local cmd = tool_call.command
|
||||||
if not cmd then
|
if not cmd then
|
||||||
return "[executeCommand] Missing 'command'."
|
return "[executeCommand] Missing 'command'."
|
||||||
end
|
end
|
||||||
local handle = io.popen(cmd)
|
|
||||||
|
-- Capture stderr and stdout together by redirecting stderr to stdout
|
||||||
|
-- This will help diagnose if there's an error causing no output
|
||||||
|
cmd = cmd .. " 2>&1"
|
||||||
|
|
||||||
|
-- Attempt to popen the command
|
||||||
|
local handle = io.popen(cmd, "r")
|
||||||
if not handle then
|
if not handle then
|
||||||
return string.format("Tool [executeCommand '%s'] FAILED to popen.", cmd)
|
return string.format("Tool [executeCommand '%s'] FAILED to popen.", cmd)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Read the full output (stdout + stderr)
|
||||||
local result = handle:read("*a") or ""
|
local result = handle:read("*a") or ""
|
||||||
handle:close()
|
|
||||||
return string.format("Tool [executeCommand '%s'] Result:\n%s", cmd, result)
|
-- Attempt to close, capturing exit info
|
||||||
|
local _, exit_reason, exit_code = handle:close()
|
||||||
|
|
||||||
|
-- Provide a richer summary including exit code and reason
|
||||||
|
return string.format(
|
||||||
|
"Tool [executeCommand '%s'] exited with code %s (%s)\n%s",
|
||||||
|
cmd,
|
||||||
|
tostring(exit_code),
|
||||||
|
tostring(exit_reason),
|
||||||
|
result
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ M.available_tools = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "editFile",
|
name = "editFile",
|
||||||
usage = "Overwrite an entire file's content. Provide { tool='editFile', path='...', content='...' }",
|
usage = "Overwrite an entire file's content. Provide { tool='editFile', path='...', content='...' }, Allways include the whole file content",
|
||||||
explanation = "Use this when you want to replace a file with new content."
|
explanation = "Use this when you want to replace a file with new content."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -25,7 +25,7 @@ M.available_tools = {
|
|||||||
{
|
{
|
||||||
name = "executeCommand",
|
name = "executeCommand",
|
||||||
usage = "Run a shell command. Provide { tool='executeCommand', command='...' }",
|
usage = "Run a shell command. Provide { tool='executeCommand', command='...' }",
|
||||||
explanation = "Use with caution, especially for destructive operations (rm, sudo, etc.)."
|
explanation = "Just run one single command per tool invocation, without comment. It must be a single line. Use with caution, especially for destructive operations (rm, sudo, etc.)."
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,12 +86,12 @@ local function send_did_open(bufnr, client_id, path, filetype)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function send_did_change(bufnr, client_id)
|
local function send_did_change(bufnr, client_id, path)
|
||||||
local client = lsp.get_client_by_id(client_id)
|
local client = lsp.get_client_by_id(client_id)
|
||||||
if not client then return end
|
if not client then return end
|
||||||
|
|
||||||
local text = table.concat(api.nvim_buf_get_lines(bufnr, 0, -1, false), "\n")
|
local text = table.concat(api.nvim_buf_get_lines(bufnr, 0, -1, false), "\n")
|
||||||
local uri = vim.uri_from_bufnr(bufnr)
|
local uri = vim.uri_from_fname(path)
|
||||||
local version = 1
|
local version = 1
|
||||||
|
|
||||||
client.rpc.notify("textDocument/didChange", {
|
client.rpc.notify("textDocument/didChange", {
|
||||||
@@ -105,6 +105,7 @@ local function send_did_change(bufnr, client_id)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Use vim.wait to allow the event loop to process LSP diagnostics
|
||||||
local function wait_for_diagnostics(bufnr, timeout_ms)
|
local function wait_for_diagnostics(bufnr, timeout_ms)
|
||||||
local done = false
|
local done = false
|
||||||
local result_diags = {}
|
local result_diags = {}
|
||||||
@@ -121,12 +122,9 @@ local function wait_for_diagnostics(bufnr, timeout_ms)
|
|||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
local waited = 0
|
vim.wait(timeout_ms, function()
|
||||||
local interval = 50
|
return done
|
||||||
while not done and waited < timeout_ms do
|
end, 50)
|
||||||
vim.cmd(("sleep %d m"):format(interval))
|
|
||||||
waited = waited + interval
|
|
||||||
end
|
|
||||||
|
|
||||||
pcall(api.nvim_del_augroup_by_id, augrp)
|
pcall(api.nvim_del_augroup_by_id, augrp)
|
||||||
return result_diags
|
return result_diags
|
||||||
@@ -164,8 +162,8 @@ function M.lsp_check_file_content(path, new_content, timeout_ms)
|
|||||||
return "(LSP) " .. err2
|
return "(LSP) " .. err2
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Optionally do a didChange
|
-- Optionally do a didChange with consistent URI
|
||||||
send_did_change(bufnr, client_id)
|
send_did_change(bufnr, client_id, path)
|
||||||
|
|
||||||
local diags = wait_for_diagnostics(bufnr, timeout_ms or 2000)
|
local diags = wait_for_diagnostics(bufnr, timeout_ms or 2000)
|
||||||
local diag_str = diagnostics_to_string(diags)
|
local diag_str = diagnostics_to_string(diags)
|
||||||
|
|||||||
@@ -3,14 +3,41 @@ local robust_lsp = require("chatgpt_nvim.tools.lsp_robust_diagnostics")
|
|||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
-- Function to escape all Lua pattern magic characters:
|
||||||
|
local function escape_lua_pattern(s)
|
||||||
|
return s:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
|
||||||
|
end
|
||||||
|
|
||||||
local function search_and_replace(original, replacements)
|
local function search_and_replace(original, replacements)
|
||||||
local updated = original
|
local updated = original
|
||||||
|
local info_msgs = {}
|
||||||
|
|
||||||
for _, r in ipairs(replacements) do
|
for _, r in ipairs(replacements) do
|
||||||
local search_str = r.search or ""
|
local search_str = r.search or ""
|
||||||
local replace_str = r.replace or ""
|
local replace_str = r.replace or ""
|
||||||
updated = updated:gsub(search_str, replace_str)
|
|
||||||
|
-- Escape special pattern chars to ensure literal matching:
|
||||||
|
local escaped_search = escape_lua_pattern(search_str)
|
||||||
|
|
||||||
|
local replacement_count = 0
|
||||||
|
updated, replacement_count = updated:gsub(escaped_search, replace_str)
|
||||||
|
|
||||||
|
-- If the string was not found, append an info message
|
||||||
|
if replacement_count == 0 then
|
||||||
|
table.insert(info_msgs, string.format(
|
||||||
|
"[replace_in_file Info] The string '%s' was NOT found in the file and was not replaced.",
|
||||||
|
search_str
|
||||||
|
))
|
||||||
|
else
|
||||||
|
table.insert(info_msgs, string.format(
|
||||||
|
"[replace_in_file Info] The string '%s' was replaced %d time(s).",
|
||||||
|
search_str,
|
||||||
|
replacement_count
|
||||||
|
))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return updated
|
|
||||||
|
return updated, table.concat(info_msgs, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
||||||
@@ -20,8 +47,8 @@ M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file
|
|||||||
if not path or #replacements == 0 then
|
if not path or #replacements == 0 then
|
||||||
return "[replace_in_file] Missing 'path' or 'replacements'."
|
return "[replace_in_file] Missing 'path' or 'replacements'."
|
||||||
end
|
end
|
||||||
local root = vim.fn.getcwd()
|
|
||||||
|
|
||||||
|
local root = vim.fn.getcwd()
|
||||||
if not is_subpath(root, path) then
|
if not is_subpath(root, path) then
|
||||||
return string.format("Tool [replace_in_file for '%s'] REJECTED. Path outside project root.", path)
|
return string.format("Tool [replace_in_file for '%s'] REJECTED. Path outside project root.", path)
|
||||||
end
|
end
|
||||||
@@ -31,7 +58,8 @@ M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file
|
|||||||
return string.format("[replace_in_file for '%s'] FAILED. Could not read file.", path)
|
return string.format("[replace_in_file for '%s'] FAILED. Could not read file.", path)
|
||||||
end
|
end
|
||||||
|
|
||||||
local updated_data = search_and_replace(orig_data, replacements)
|
-- Now we get not only the updated data but also info messages about replacements
|
||||||
|
local updated_data, info_messages = search_and_replace(orig_data, replacements)
|
||||||
handler.write_file(path, updated_data, conf)
|
handler.write_file(path, updated_data, conf)
|
||||||
|
|
||||||
local msg = {}
|
local msg = {}
|
||||||
@@ -40,6 +68,11 @@ M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file
|
|||||||
msg[#msg+1] = string.format("<final_file_content path=\"%s\">\n%s\n</final_file_content>", path, updated_data)
|
msg[#msg+1] = string.format("<final_file_content path=\"%s\">\n%s\n</final_file_content>", path, updated_data)
|
||||||
msg[#msg+1] = "\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.\n"
|
msg[#msg+1] = "\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.\n"
|
||||||
|
|
||||||
|
-- If there are any info messages (strings not found, etc.), include them
|
||||||
|
if info_messages and info_messages ~= "" then
|
||||||
|
msg[#msg+1] = "\nAdditional info about the replacement operation:\n" .. info_messages .. "\n"
|
||||||
|
end
|
||||||
|
|
||||||
if conf.auto_lint then
|
if conf.auto_lint then
|
||||||
local diag_str = robust_lsp.lsp_check_file_content(path, updated_data, 3000)
|
local diag_str = robust_lsp.lsp_check_file_content(path, updated_data, 3000)
|
||||||
msg[#msg+1] = "\n" .. diag_str
|
msg[#msg+1] = "\n" .. diag_str
|
||||||
|
|||||||
Reference in New Issue
Block a user