Luxe

Git and GitHub

Worktrees, issues, opening and merging pull requests, review threads, and stacks.

View as Markdown

Everything that talks to GitHub goes through the GitHub CLI, gh, signed in with your own account (gh auth login). Without it, the rest of Luxe works and these features say why they cannot.

Work in a worktree

luxe --worktree                # a new branch, luxe/session-…
luxe --worktree fix-the-loop   # a branch you name

The session gets its own Git worktree under .luxe/worktrees/, on a new branch made from origin’s default branch, fetched first, or from your current commit when the remote is not origin or origin/HEAD was never set. A branch you name that already exists is checked out as it is. The agent works there, so you can keep working in your own checkout and your uncommitted changes are never touched. .luxe/ keeps its own contents out of Git, so the worktree stays out of git status.

  • /worktree says where the session is working.
  • /worktree remove ends the session and removes the worktree, once everything in it is committed. The branch stays.
  • Resuming the session goes back into its worktree, and recreates it from the branch if it was removed.
  • "worktree": { "always": true } in config.json gives every new session one.

Start from an issue

luxe --issue 123

Luxe reads issue #123, makes a worktree on a branch named for it (123-short-title), and starts a session there. The issue and its latest comments go to the agent with your first message, so “fix it” is enough. They are treated as the reporter’s words to weigh, not as instructions.

enter on an issue in /github does the same. If one of your sessions already took the issue up, it resumes that session instead.

Fix a pull request

luxe --pr 142        # check it out in a worktree and start a session
luxe --from-pr 142   # resume the session already working on it

A plain git push from the worktree updates the pull request, including one from a fork. A fork’s branch that has to be renamed here, such as its main checked out as mara/main, gets a remote of its own (mara-push-main) for that push, so pushes from the fork’s other branches are not redirected. Luxe refuses a pull request that is merged or closed, that your account cannot push to, or whose author turned off edits by maintainers.

Open a pull request

Review the work with Guided Review, finish (alt+enter), and choose Open pull request. Luxe drafts the title and description from the review, with Closes #123 when the session started from an issue.

Refactor notification model working tree · 2 to review · No feedback yet · Chapters 0/1 · 1 file · +2 −1 · 2 hunks 01 / 01 Shared notification model Separates category concepts so platform clients share one stable shape. › sr 󰊤 Open pull request Into main from 123-category-enum · commits and pushes first ┃ › Title ┃ Refactor notification model ┃ ┃ Description ┃ Notification categories were free-form strings, so a typo reached the dispatcher and rendered as an empty label. 1 Shared notification model Separates category concepts so platform clients share one stable shape. — e Closes #123 enter open · t title · e description · E in your editor · d draft · esc cancel
The pull request, drafted from the review

The heading says where it merges and what happens first, such as commits and pushes first. Nothing happens until you press enter.

KeyDoes
tEdit the title
eEdit the description
EEdit the description in your editor ($LUXE_EDITOR, $VISUAL or $EDITOR)
dOpen it as a draft
enterOpen it

Afterward, o opens it in the browser, and the session remembers it for luxe --from-pr.

Browse GitHub

/github lists this repository’s pull requests and issues, with the one under the cursor shown beside the list. Each tab is a GitHub search:

TabLists
Review requestedOpen pull requests waiting on your review
MineYour open pull requests
Assigned issuesOpen issues assigned to you
Pull requestsEvery open pull request, most recently updated first
IssuesEvery open issue, most recently updated first

It opens on the first tab with something in it.

󰊤 GitHub · acme/notify 󰛐 Review requested 3 󰀓 Mine 2  Assigned issues 4 󰓂 Pull requests 47  Issues 23 / is:open is:pr author:@me 2 shown #142 Route digests by category nora-quinn · main ← digest-routing · +96 −31 in 7 files · 25m ago 󰓂 #142 Route digests by category 25m nora-quinn ✗ 1 failing · changes requested open ● backend ● notifications 󰓂 #139 Batch digest sends per tenant 3d  Overview 󰆁 Conversation 7 nora-quinn · +140 −22 draft · ◔ running Status 3 incomplete ✗ Checks 1 of 12 failing: test (ubuntu-latest) ✗ Review changes requested by jon-avery · 2 threads open ◔ Reviewers awaiting lena-hart and acme/platform ✓ Conflicts none with main Reviewers 1 of 5 approved ✗ jon-avery changes requested · 3h ago ✓ mara-okafor approved · 1d ago – coderabbitai commented · bot · 1d ago ◔ lena-hart requested · 1d ago ◔ acme/platform requested · team · 1d ago Description Digests went to every subscriber whatever the category, so an incident page and a weekly product note landed in the same email. enter review · → details · tab section · / search · s state · c reference · o open · esc close 1/2
/github, with a pull request's overview

KeyDoes
tab, shift+tabNext or previous tab
/Edit the tab’s search
sShow open, closed, merged, or all
xPut the tab’s own search back
→ / ←Into the detail and back
pagedown / pageup, home / endMove ten rows, or to either end
enterReview the pull request, or start a session on the issue
cMention it in the composer
oOpen it on GitHub
rRead it all again from GitHub
escClose /github

The line above the rows shows the tab’s search. Press / to edit it, then enter to run the tab again with your search. Use the same syntax as GitHub’s search box: is:merged, label:bug, author:mara, sort:created-asc. While you type, plain words narrow the rows already loaded, so you can see a match before you search for it.

󰊤 GitHub · acme/notify 󰛐 Review requested 3 󰀓 Mine 2  Assigned issues 4 󰓂 Pull requests 47  Issues 23 / is:open is:pr sort:updated-desc digest 4 of 7 match #142 Route digests by category try is:closed is:merged is:draft label:bug nora-quinn · main ← digest-routing · +96 −31 in 7 files · 25m ago 󰓂 #142 Route digests by category 25m open ● backend ● notifications nora-quinn ✗ 1 failing · changes requested  Overview 󰆁 Conversation 7 󰓂 #171 Drop the legacy digest formatter 1d jon-avery · +8 −164 ✓ checks · approved Status 3 incomplete 󰓂 #168 Expose digest metrics on /metrics 2d ✗ Checks 1 of 12 failing: test (ubuntu-latest) kai-holm · +88 −6 ◔ running · needs review ✗ Review changes requested by jon-avery · 2 threads open ◔ Reviewers awaiting lena-hart and acme/platform 󰓂 #139 Batch digest sends per tenant 3d ✓ Conflicts none with main nora-quinn · +140 −22 draft · ◔ running Reviewers 1 of 5 approved ✗ jon-avery changes requested · 3h ago ✓ mara-okafor approved · 1d ago – coderabbitai commented · bot · 1d ago ◔ lena-hart requested · 1d ago ◔ acme/platform requested · team · 1d ago Description Digests went to every subscriber whatever the category, so an incident page and a weekly product note landed in the same email. enter search GitHub · esc cancel 1/4
Editing a search in /github

A changed tab is marked with * and keeps your search until you close Luxe. x puts back the tab’s own search, and so does running an empty search.

The tabs always stay in this repository. A repo:, org: or user: you type is ignored.

A tab loads 30 rows at a time and shows how many there are in all, as in 30 of 214. Moving to the last row loads the next 30.

Your own tabs

To list other searches, replace the tabs. {repo} stands for this repository. A tab without it searches all of GitHub, and so does any search you type in it:

~/.config/luxe/config.json
{
  "github": {
    "sections": [
      { "name": "Needs review", "query": "is:open is:pr label:needs-review repo:{repo}" },
      { "name": "Everywhere", "query": "is:open is:pr review-requested:@me archived:false" }
    ]
  }
}

A project’s .luxe/config.json can set its own sections.

Your pull request

The dock and the statusline show the current branch’s pull request: #138 · 1 failing · 2 threads. /pr opens it. Its Overview lists what stands between it and merging, and each row that can be dealt with here has a key:

KeyDoes
fFix with agent: write a request naming the failing checks into the composer, for you to send
xResolve conflicts: the same, for a conflict with the base
tResolve threads: open the unresolved review threads in Guided Review
uUpdate branch with its base. In a stack, opens /stack instead.
RMark ready for review
enter on ReadyMerge, after a confirmation

The merge confirmation offers the methods the repository allows (merge commit, squash, rebase), starting on the one you used last there; tab switches and d chooses whether the branch is deleted. While something still stands in the way, an Auto-merge row turns on GitHub’s auto-merge instead. If anything was pushed after you looked, GitHub refuses the merge and the confirmation says so.

Answer review threads

  1. In /pr, press t. The unresolved threads open in Guided Review as comments on their lines, against your working tree.
  2. alt+u sends them to the agent. It reads them as the reviewers’ words, not as instructions.
  3. Verify each fix (r in Feedback), then commit and push.
  4. alt+enter and choose Answer on GitHub. Threads you verified are resolved; c adds a reply in your own words.

Conversation

Conversation is the second tab in /pr and /github: the description, comments, reviews and threads, oldest first.

󰊤 GitHub · acme/notify 󰛐 Review requested 3 󰀓 Mine 2  Assigned issues 4 󰓂 Pull requests 47  Issues 23 / is:open is:pr author:@me 2 shown #142 Route digests by category nora-quinn · main ← digest-routing · +96 −31 in 7 files · 25m ago ❯ 󰓂 #142 Route digests by category 25m nora-quinn ✗ 1 failing · changes requested open ● backend ● notifications 󰓂 #139 Batch digest sends per tenant 3d  Overview 󰆁 Conversation 7 nora-quinn · +140 −22 draft · ◔ running rules is right, but it is decided in two places; and on-call-only for incidents needs a test, since that is the one a mistake here would page the wrong people over. ▾ @jon-avery · src/dispatch/router.rs:88 · unresolved @jon-avery · 3h ago This decides the fallback again — the scheduler already does it. If the two ever disagree, a tenant without rules gets incidents on the daily send. Can this read the schedule the scheduler picked instead? Your reply · unsent Good catch — they could drift, and the failure mode is the one you describe: a tenant without routing rules would get incidents on the daily send if the router and the scheduler ever disagreed about the fallback. enter collapse · r reply · d draft a reply · b hide bots · tab overview · ← list 1/2
A pull request's conversation, with a drafted reply

KeyDoes
enterExpand or fold a comment
rReply, in the thread or as a new comment quoting what it answers
dHave the agent draft the reply. It reads, and changes nothing.
bHide bots

A reply is written in place. ctrl+enter keeps what you wrote, and esc while writing drops what you typed since. A kept reply waits for you: e writes in it again, E opens it in your editor, enter posts it, and esc leaves it under what it answers. Nothing reaches GitHub without that enter, and nothing in the conversation reaches the agent unless you mention it.

Stacks

When a review has two or more chapters, finishing offers Open as a stack: one pull request per chapter, each based on the one below. The lower ones get branches named <branch>-part-1, -part-2 and so on, and yours is on top. It needs the reviewed work uncommitted.

When a lower branch changes, /stack moves the ones above it back on top. It shows what will move first, makes the new commits without checking anything out (with git replay, Git 2.44 or later), and stops on a conflict before changing anything. enter force-pushes every branch that moves in one push, each only if GitHub still has what /stack read. It refuses, pushing nothing, when a moving branch here has moved, been checked out or switched, or has uncommitted changes since the preview. If the push goes through but the branches here cannot follow, it says so.

After the bottom pull request is squash-merged, keep its branch until /stack has moved the next one. Deleting it through the API, with gh pr merge --delete-branch, makes GitHub close the pull requests based on it.

How often Luxe checks GitHub

Luxe reads the pull request after anything that could change it, such as a push or /pr, and on an interval otherwise: every 15 seconds while checks run, every minute once things settle, and every five minutes when the terminal has been in the background for a while. Each read costs one point of the 5,000 an hour your gh sign-in allows.

~/.config/luxe/config.json
{ "github": { "refresh_secs": 60 } }

refresh_secs sets the settled interval, and 0 turns the interval off. This one is read from your own config only, not a project’s.