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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions ai-code-backends.el
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ so the CLI itself handles the installation details."
:config "~/.claude.json"
:agent-file "CLAUDE.md"
:upgrade "npm install -g @anthropic-ai/claude-code@latest"
:install-skills ai-code-claude-code-install-skills
:install-skills nil
:cli "claude")
(gemini
:label "Gemini CLI"
Expand Down Expand Up @@ -584,16 +584,32 @@ ARG is the prefix argument to pass to the upgrade function."
"Fallback skills installation for backend LABEL.
Prompt user for a skills repository URL and ask the AI CLI session
to read the repo README and install the skills."
(let* ((url (read-string
(format "Skills repo URL for %s: " label)
(ai-code--manage-backend-skills-fallback label 'install))

(defun ai-code--manage-backend-skills-fallback (label action)
"Fallback backend skills management for LABEL and ACTION.
ACTION should be the symbol `install' or `uninstall'."
(let* ((action-name
(pcase action
('install "install")
('uninstall "uninstall")
(_ (user-error
"Invalid backend skills action: %S; expected `install' or `uninstall'"
action))))
(url (read-string
(format "Skills repo URL for %s %s: " label action-name)
nil nil "https://github.com/obra/superpowers"))
(default-prompt
(format
"Please read the README of %s and install/setup the skills described there for %s. Follow the installation instructions in the README."
url label))
(if (eq action 'uninstall)
(format
"Please read the README of %s and uninstall/remove the skills described there for %s. Follow the repository instructions to remove any installed skill files and cleanup related configuration."
url label)
(format
"Please read the README of %s and install/setup the skills described there for %s. Follow the installation instructions in the README."
url label)))
(prompt (if (called-interactively-p 'interactive)
(ai-code-read-string
(format "Edit install-skills prompt for %s: " label)
(format "Edit %s-skills prompt for %s: " action-name label)
default-prompt)
default-prompt)))
(ai-code-cli-send-command prompt)))
Expand All @@ -606,15 +622,25 @@ If the backend defines an :install-skills property, use it:
- symbol: call the function.
Otherwise fall back to prompting the AI session to install from a
skills repository URL."
;; DONE: firstly ask user if it is install or uninstall, then ask for the repo URL if needed, and finally run install / uninstall using corresponding prompt.
(interactive)
(let* ((spec (ai-code--backend-spec ai-code-selected-backend)))
(if (not spec)
(user-error "No backend is currently selected")
(ai-code--ensure-backend-loaded spec)
(let* ((plist (cdr spec))
(install-skills (plist-get plist :install-skills))
(label (ai-code-current-backend-label)))
(label (ai-code-current-backend-label))
(action-choice (completing-read
(format "Manage skills for %s: " label)
'("install" "uninstall")
nil t nil nil "install"))
(action (if (string= action-choice "uninstall")
'uninstall
'install)))
(cond
((eq action 'uninstall)
(ai-code--manage-backend-skills-fallback label 'uninstall))
((stringp install-skills)
(compile install-skills)
(message "Running skills installation for %s" label))
Expand Down
38 changes: 27 additions & 11 deletions ai-code-git.el
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Candidate values:
(declare-function ai-code--git-root "ai-code-file" (&optional dir))

(defvar ai-code-files-dir-name)
(defvar ai-code-pr-title-history nil
"Minibuffer history for optional PR titles.")

(defun ai-code--git-ignored-repo-file-p (file root)
"Return non-nil when FILE should be ignored for repo candidates under ROOT."
Expand Down Expand Up @@ -183,7 +185,7 @@ branch for the current branch PR. Otherwise, ask for the relevant pull
request or issue URL."
(let* ((review-mode (ai-code--pull-or-review-pr-mode-choice))
(init-prompt
(if (eq review-mode 'send-current-branch-pr)
(if (eq review-mode 'send-current-branch-pr)
(progn
(unless (magit-toplevel)
(user-error "Not inside a Git repository"))
Expand All @@ -192,9 +194,14 @@ request or issue URL."
(ai-code--default-pr-target-branch current-branch))
(target-branch
(ai-code-read-string "Target branch to merge into: "
default-target-branch)))
default-target-branch))
(pr-title
(read-string
"PR title (optional, leave empty for AI to generate): "
nil
'ai-code-pr-title-history)))
(ai-code--build-send-current-branch-pr-init-prompt
review-source current-branch target-branch)))
review-source current-branch target-branch pr-title)))
(let* ((url-prompt (ai-code--pull-or-review-url-prompt review-mode))
(target-url (ai-code-read-string url-prompt)))
(ai-code--build-pr-init-prompt review-source target-url review-mode))))
Expand All @@ -207,6 +214,7 @@ request or issue URL."
(defun ai-code--pull-or-review-pr-mode-choice ()
"Prompt user to choose analysis mode for a pull request or issue."
;; DONE: add a choice: send out PR for current branch. The feature will ask user the target branch to merge. By default, it should be parent branch of current branch. AI should send out PR with description. The description should looks like it's written by the author, and it should be short.
;; DONE: for send out PR feature, it should ask user about the PR title. If user does not provide one, AI should generate a concise title based on the code change
(let* ((review-mode-alist '(("Review the PR" . review-pr)
("Check unresolved feedback" . check-feedback)
("Investigate issue" . investigate-issue)
Expand Down Expand Up @@ -298,24 +306,32 @@ request or issue URL."
"Create the pull request using the backend's PR creation capability. "
"Do not treat this as a PR review flow before the PR exists."))))

(defun ai-code--build-send-current-branch-pr-init-prompt (review-source current-branch target-branch)
"Build a PR creation prompt for REVIEW-SOURCE, CURRENT-BRANCH, and TARGET-BRANCH."
(defun ai-code--build-send-current-branch-pr-init-prompt
(review-source current-branch target-branch &optional pr-title)
"Build a PR creation prompt.
REVIEW-SOURCE, CURRENT-BRANCH, TARGET-BRANCH, and PR-TITLE
define the PR request."
(let ((source-instruction
(ai-code--send-current-branch-pr-source-instruction review-source)))
(ai-code--send-current-branch-pr-source-instruction review-source))
(title-instruction
(if (string-empty-p (or pr-title ""))
"2. Generate a concise PR title based on the code change.\n"
(format "2. Use this PR title exactly: %s\n" pr-title))))
(format "Create a pull request from branch %s into %s.

%s

PR Creation Steps:
1. Inspect the current branch changes and open or send out a pull request into %s.
2. Write a concise PR description that sounds like it was written by the author, but do not make it too short.
3. Keep the description focused on the problem, the approach, and the most important verification, with enough detail for reviewers to understand the change quickly.
4. Aim for a compact but complete description, roughly a short summary plus 2 to 3 brief supporting paragraphs or bullet points.
5. Return the final PR URL and the exact description that was used."
%s3. Write a concise PR description that sounds like it was written by the author, but do not make it too short.
4. Keep the description focused on the problem, the approach, and the most important verification, with enough detail for reviewers to understand the change quickly.
5. Aim for a compact but complete description, roughly a short summary plus 2 to 3 brief supporting paragraphs or bullet points.
6. Return the final PR URL, the exact PR title, and the exact description that were used."
current-branch
target-branch
source-instruction
target-branch)))
target-branch
title-instruction)))

;;;###autoload
(defun ai-code-pull-or-review-diff-file ()
Expand Down
98 changes: 70 additions & 28 deletions ai-code-prompt-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,9 @@ based on the task name. Otherwise, use cleaned-up task name directly."
:type 'boolean
:group 'ai-code)

(defvar ai-code-task-search-directory-history nil
"Minibuffer history for task content search directories.")

(defun ai-code--get-files-directory ()
"Get the task directory path.
If in a git repository, return `.ai.code.files/` under git root.
Expand Down Expand Up @@ -656,38 +659,77 @@ Returns the selected directory path."
(when (member task-name task-file-candidates)
(expand-file-name task-name ai-code-files-dir)))

(defun ai-code--read-task-search-directory (ai-code-files-dir)
"Read a target directory for searching task content.
Default to AI-CODE-FILES-DIR and keep a dedicated directory history."
(let* ((input
(read-string "Directory to search org files: "
ai-code-files-dir
'ai-code-task-search-directory-history
ai-code-files-dir))
(target-dir (expand-file-name (if (string-empty-p input)
ai-code-files-dir
input))))
(unless (file-directory-p target-dir)
(user-error "Search directory does not exist: %s" target-dir))
target-dir))

(defun ai-code--build-task-search-prompt (target-dir search-description)
"Build a prompt for searching org files in TARGET-DIR.
SEARCH-DESCRIPTION describes what content the AI should search for."
(format
(concat
"Search the content of all .org files recursively under directory: %s\n"
"Search target description: %s\n"
"Focus on matching content inside the files, not just file names.\n"
"Return the relevant file paths, matched excerpts, and a concise summary.")
target-dir
search-description))

(defun ai-code--search-task-files-with-ai (ai-code-files-dir)
"Prompt for task file search inputs and send a search request to AI."
(let* ((target-dir (ai-code--read-task-search-directory ai-code-files-dir))
(search-description (ai-code-read-string "Search description for .org files: "))
(default-prompt (ai-code--build-task-search-prompt target-dir search-description))
(confirmed-prompt (ai-code-read-string "Confirm search prompt: " default-prompt)))
(ai-code--execute-command confirmed-prompt)))

;;;###autoload
(defun ai-code-create-or-open-task-file ()
(defun ai-code-create-or-open-task-file (&optional arg)
"Create or open an AI task file.
Prompts for a task name. If empty, opens the task directory.
If non-empty, optionally prompts for a URL, generates a filename
using GPTel, and creates the task file."
(interactive)
(let* ((ai-code-files-dir (ai-code--ensure-files-directory))
(task-file-candidates (ai-code--task-file-candidates ai-code-files-dir))
(task-name (ai-code--read-task-name task-file-candidates))
(existing-task-file (ai-code--existing-task-file-path task-name task-file-candidates ai-code-files-dir)))
(cond
((string-empty-p task-name)
(dired-other-window ai-code-files-dir)
(message "Opened task directory: %s" ai-code-files-dir))
(existing-task-file
(ai-code--open-or-create-task-file existing-task-file task-name task-name ""))
(t
(let* ((task-url (read-string "URL (optional, press Enter to skip): "))
(generated-filename (ai-code--generate-task-filename task-name))
(confirmed-filename (read-string "Confirm task filename (end with / to create subdirectory): " generated-filename))
(current-dir (expand-file-name default-directory))
(selected-dir (ai-code--select-task-target-directory ai-code-files-dir current-dir))
(create-dir-only-p (string-suffix-p "/" confirmed-filename))
(task-file (expand-file-name confirmed-filename selected-dir)))
(if create-dir-only-p
(let ((subdir (expand-file-name (directory-file-name confirmed-filename) selected-dir)))
(unless (file-directory-p subdir)
(make-directory subdir t))
(dired-other-window subdir)
(message "Opened directory: %s" subdir))
(ai-code--open-or-create-task-file task-file confirmed-filename task-name task-url)))))))
using GPTel, and creates the task file.
With prefix ARG, prompt AI to search org file content under a target directory."
;; DONE: when C-u pressed, let AI search content inside ai-code-files-dir given search target description. User firstly confirm the target dir to search (it support dedicate history entered), by default it is ai-code-files-dir, then input search description, it will build up prompt to search .org files inside target directory, and user confirm with ai-code-read-string. After that, the prompt will be sent to ai coding session to perform search.
(interactive "P")
(let ((ai-code-files-dir (ai-code--ensure-files-directory)))
(if arg
(ai-code--search-task-files-with-ai ai-code-files-dir)
(let* ((task-file-candidates (ai-code--task-file-candidates ai-code-files-dir))
(task-name (ai-code--read-task-name task-file-candidates))
(existing-task-file (ai-code--existing-task-file-path task-name task-file-candidates ai-code-files-dir)))
(cond
((string-empty-p task-name)
(dired-other-window ai-code-files-dir)
(message "Opened task directory: %s" ai-code-files-dir))
(existing-task-file
(ai-code--open-or-create-task-file existing-task-file task-name task-name ""))
(t
(let* ((task-url (read-string "URL (optional, press Enter to skip): "))
(generated-filename (ai-code--generate-task-filename task-name))
(confirmed-filename (read-string "Confirm task filename (end with / to create subdirectory): " generated-filename))
(current-dir (expand-file-name default-directory))
(selected-dir (ai-code--select-task-target-directory ai-code-files-dir current-dir))
(create-dir-only-p (string-suffix-p "/" confirmed-filename))
(task-file (expand-file-name confirmed-filename selected-dir)))
(if create-dir-only-p
(let ((subdir (expand-file-name (directory-file-name confirmed-filename) selected-dir)))
(unless (file-directory-p subdir)
(make-directory subdir t))
(dired-other-window subdir)
(message "Opened directory: %s" subdir))
(ai-code--open-or-create-task-file task-file confirmed-filename task-name task-url)))))))))

;;;###autoload
(add-to-list 'auto-mode-alist
Expand Down
4 changes: 2 additions & 2 deletions ai-code.el
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ Shows the current backend label to the right."
("z" "Switch to AI CLI (C-u: hide)" ai-code-cli-switch-to-buffer-or-hide)
("s" ai-code-select-backend :description ai-code--select-backend-description)
("u" "Install / Upgrade AI CLI" ai-code-upgrade-backend)
("S" "Install skills for backend" ai-code-install-backend-skills)
("S" "(Un)Install skills for backend" ai-code-install-backend-skills)
("g" "Open backend config (eg. add mcp)" ai-code-open-backend-config)
("G" "Open backend repo agent file" ai-code-open-backend-agent-file)
("|" "Apply prompt on file" ai-code-apply-prompt-on-current-file))
Expand All @@ -532,7 +532,7 @@ Shows the current backend label to the right."
("v" "GitHub PR AI Action" ai-code-pull-or-review-diff-file)
("!" "Run Current File or Command" ai-code-run-current-file-or-shell-cmd)
("b" "Build/Test/Lint (AI follow-up)" ai-code-build-or-test-project)
("K" "Create or open task file" ai-code-create-or-open-task-file)
("K" "Create/Open task file (C-u: Search)" ai-code-create-or-open-task-file)
("n" "Take notes from AI session region" ai-code-take-notes)
(":" "Speech to text input" ai-code-speech-to-text-input))

Expand Down
Loading
Loading