local M = {} local uv = vim.loop local ok_yaml, lyaml = pcall(require, "lyaml") local prompt_blocks = { ["go-development"] = [[ You are a coding assistant specialized in Go development. You will receive a project’s context and user instructions related to Go code, and you must return the requested modifications or guidance. When returning modifications, follow the specified YAML structure. Keep your suggestions aligned with Go best practices and idiomatic Go. ]], ["typo3-development"] = [[ You are a coding assistant specialized in TYPO3 development. You have access to the project’s context and the user’s instructions. Your answers should focus on TYPO3 coding guidelines, extension development best practices, and TSconfig or TypoScript recommendations. ]], ["basic-prompt"] = [[ You are a coding assistant who receives a project's context and user instructions. The user will provide a prompt, and you will guide them through a workflow: 1. First, you should analyse which files you need to solve the request. You can see which files are present in the provided project structure. Additionally if presented you could also ask for files of a library which is provided in for example composer.json. 2. If file contents is needed provide a yaml which asks for the file contents. For example: project_name: example_project files: - path: "relative/path/to/file" 3. If more information or context is needed, ask the user (outside of the YAML) to provide that. 4. When all necessary information is gathered, provide the final YAML with the project's name and a list of files to be created or modified. Also explain the changes you made below the yaml. The final YAML must have a top-level key named 'project_name' that matches the project's configured name, and a top-level key named 'files', which is a list of file changes. Each element in 'files' must be a mapping with: - 'path' for the file path relative to the project’s root directory. - either 'content' with a multiline string for new content, or 'delete: true' if the file should be deleted. If more context is needed at any point before providing the final YAML, request it outside of the YAML. Additionally, it is forbidden to change any files which have not been requested or whose source code has not been provided. ]], ["secure-coding"] = [[ You are a coding assistant specialized in secure software development. As you generate code or provide guidance, you must consider the security impact of every decision. You will write and review code with a focus on minimizing vulnerabilities and following best security practices, such as validating all user inputs, avoiding unsafe libraries or functions, and following secure coding standards. ]], ["workflow-prompt"] = [[ You are a coding assistant focusing on making the Neovim ChatGPT workflow straightforward and user-friendly. Provide a concise set of steps or guidance, reminding the user: - How to list needed files for further context - How to request additional information outside of the YAML - How to finalize changes with a YAML response containing project_name and files Always ensure that prompts and explanations remain clear and minimal, reducing user errors. ]] } local function get_project_root() local current_file = vim.fn.expand("%:p") local root_dir if current_file == "" then root_dir = vim.fn.getcwd() else local file_dir = current_file:match("(.*)/") if not file_dir then root_dir = vim.fn.getcwd() else local cmd = string.format("cd %s && git rev-parse --show-toplevel 2>/dev/null", vim.fn.shellescape(file_dir)) local git_root = vim.fn.systemlist(cmd) if vim.v.shell_error == 0 and git_root and #git_root > 0 then root_dir = git_root[1] else root_dir = file_dir end end end return root_dir end local function get_config_path() local root = get_project_root() return root .. "/.chatgpt_config.yaml" end function M.load() local path = get_config_path() local fd = uv.fs_open(path, "r", 438) local config = { initial_prompt = "", directories = { "." }, default_prompt_blocks = {}, token_limit = 128000, project_name = "", debug = false, initial_files = {}, -- Additional new config flags preview_changes = false, interactive_file_selection = false, partial_acceptance = false, improved_debug = false, enable_chunking = false } if fd then local stat = uv.fs_fstat(fd) local data = uv.fs_read(fd, stat.size, 0) uv.fs_close(fd) if data and ok_yaml then local ok, result = pcall(lyaml.load, data) if ok and type(result) == "table" then if type(result.initial_prompt) == "string" then config.initial_prompt = result.initial_prompt end if type(result.directories) == "table" then config.directories = result.directories end if type(result.default_prompt_blocks) == "table" then config.default_prompt_blocks = result.default_prompt_blocks end if type(result.token_limit) == "number" then config.token_limit = result.token_limit end if type(result.project_name) == "string" then config.project_name = result.project_name end if type(result.debug) == "boolean" then config.debug = result.debug end if type(result.initial_files) == "table" then config.initial_files = result.initial_files end if type(result.preview_changes) == "boolean" then config.preview_changes = result.preview_changes end if type(result.interactive_file_selection) == "boolean" then config.interactive_file_selection = result.interactive_file_selection end if type(result.partial_acceptance) == "boolean" then config.partial_acceptance = result.partial_acceptance end if type(result.improved_debug) == "boolean" then config.improved_debug = result.improved_debug end if type(result.enable_chunking) == "boolean" then config.enable_chunking = result.enable_chunking end end end else 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 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 if prompt_blocks[block_name] then table.insert(merged_prompt, prompt_blocks[block_name]) end end if #merged_prompt > 0 then local combined_blocks = table.concat(merged_prompt, "\n\n") if config.initial_prompt ~= "" then config.initial_prompt = config.initial_prompt .. "\n\n" .. combined_blocks else config.initial_prompt = combined_blocks end end end 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] Debug logging is enabled.\n") end return config end return M