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