main
  1;;; pi-from-github.el --- Start pi sessions from GitHub PRs/issues -*- lexical-binding: t -*-
  2;;; Commentary:
  3;; Start a pi-coding-agent session from a GitHub PR or issue.
  4;; Uses `gh` CLI for listing PRs/issues.
  5;;; Code:
  6
  7(require 'json)
  8(require 'project)
  9
 10;; Declare pi-coding-agent functions
 11(declare-function pi-coding-agent--setup-session "pi-coding-agent")
 12(declare-function pi-coding-agent--show-session-buffers "pi-coding-agent")
 13
 14(defun pi-from-github--repo-root ()
 15  "Return the git repository root for the current project."
 16  (let ((default-directory (if (and (fboundp 'project-current) (project-current))
 17                               (project-root (project-current))
 18                             default-directory)))
 19    (string-trim (shell-command-to-string "git rev-parse --show-toplevel"))))
 20
 21(defun pi-from-github--in-github-repo-p ()
 22  "Return non-nil if current project is a GitHub repository."
 23  (let ((default-directory (pi-from-github--repo-root)))
 24    (string-match-p "github\\.com"
 25                    (shell-command-to-string "git remote -v"))))
 26
 27(defun pi-from-github--fetch-prs ()
 28  "Fetch open PRs from current GitHub repo."
 29  (let* ((default-directory (pi-from-github--repo-root))
 30         (output (shell-command-to-string
 31                  "gh pr list --limit=500 --json number,title,author,headRefName,isDraft")))
 32    (json-read-from-string output)))
 33
 34(defun pi-from-github--fetch-issues ()
 35  "Fetch open issues from current GitHub repo."
 36  (let* ((default-directory (pi-from-github--repo-root))
 37         (output (shell-command-to-string
 38                  "gh issue list --limit=500 --json number,title,author,labels")))
 39    (json-read-from-string output)))
 40
 41(defun pi-from-github--format-pr (pr)
 42  "Format PR for completion display."
 43  (let-alist pr
 44    (cons (format "#%-5d %s%s (by @%s) [%s]"
 45                  .number .title
 46                  (if (eq .isDraft t) " 🚧" "")
 47                  .author.login .headRefName)
 48          .number)))
 49
 50(defun pi-from-github--format-issue (issue)
 51  "Format ISSUE for completion display."
 52  (let-alist issue
 53    (let ((label-str (mapconcat (lambda (l) (alist-get 'name l))
 54                                (append .labels nil) ",")))
 55      (cons (format "#%-5d %s (by @%s)%s"
 56                    .number .title .author.login
 57                    (if (string-empty-p label-str) "" (format " [%s]" label-str)))
 58            .number))))
 59
 60(defun pi-from-github--org-repo ()
 61  "Return the org/repo slug (e.g. \"openshift/pipelines\") for the current GitHub repo."
 62  (let* ((default-directory (pi-from-github--repo-root))
 63         (url (string-trim (shell-command-to-string "gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null"))))
 64    (if (string-empty-p url)
 65        ;; Fallback: parse git remote
 66        (let ((remote (shell-command-to-string "git remote get-url origin 2>/dev/null")))
 67          (if (string-match "github\\.com[:/]\\([^/]+/[^/.]+\\)" remote)
 68              (match-string 1 remote)
 69            "unknown/repo"))
 70      url)))
 71
 72;;;###autoload
 73(defun pi-from-github-pr ()
 74  "Start a pi-coding-agent session from a GitHub PR.
 75Lists open PRs using `gh` and starts a pi session in the repo root.
 76Session is named org/repo#N."
 77  (interactive)
 78  (unless (pi-from-github--in-github-repo-p)
 79    (user-error "Not in a GitHub repository"))
 80  (let* ((prs (pi-from-github--fetch-prs))
 81         (candidates (mapcar #'pi-from-github--format-pr (append prs nil)))
 82         (selected (completing-read "PR: " candidates nil t))
 83         (number (cdr (assoc selected candidates))))
 84    (unless number (user-error "No PR selected"))
 85    (let* ((repo-root (pi-from-github--repo-root))
 86           (session-name (format "%s#%d" (pi-from-github--org-repo) number)))
 87      (message "Starting pi session %s in %s" session-name repo-root)
 88      (let* ((chat-buf (pi-coding-agent--setup-session repo-root session-name))
 89             (input-buf (buffer-local-value 'pi-coding-agent--input-buffer
 90                                            chat-buf)))
 91        (pi-coding-agent--show-session-buffers chat-buf input-buf)))))
 92
 93;;;###autoload
 94(defun pi-from-github-issue ()
 95  "Start a pi-coding-agent session from a GitHub issue.
 96Lists open issues using `gh` and starts a pi session in the repo root.
 97Session is named org/repo#N."
 98  (interactive)
 99  (unless (pi-from-github--in-github-repo-p)
100    (user-error "Not in a GitHub repository"))
101  (let* ((issues (pi-from-github--fetch-issues))
102         (candidates (mapcar #'pi-from-github--format-issue (append issues nil)))
103         (selected (completing-read "Issue: " candidates nil t))
104         (number (cdr (assoc selected candidates))))
105    (unless number (user-error "No issue selected"))
106    (let* ((repo-root (pi-from-github--repo-root))
107           (session-name (format "%s#%d" (pi-from-github--org-repo) number)))
108      (message "Starting pi session %s in %s" session-name repo-root)
109      (let* ((chat-buf (pi-coding-agent--setup-session repo-root session-name))
110             (input-buf (buffer-local-value 'pi-coding-agent--input-buffer
111                                            chat-buf)))
112        (pi-coding-agent--show-session-buffers chat-buf input-buf)))))
113
114(provide 'pi-from-github)
115;;; pi-from-github.el ends here