Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
66008e9
docs(agents): build each roadmap release on its own branch
hazeliscoding Sep 25, 2026
143630f
docs(roadmap): plan v0.11 Sweep with measured query costs
hazeliscoding Sep 25, 2026
947f0d8
chore(perf): log sweep timing under PRSWEEP_DEBUG and add a sweep ben…
hazeliscoding Sep 25, 2026
c62048e
docs(roadmap): record the v0.10.4 sweep baseline
hazeliscoding Sep 25, 2026
d383c9e
feat(snapshot): version cached sweeps so an update resweeps in full
hazeliscoding Sep 25, 2026
c544c86
perf(sweep): search merged PRs in parallel weekly windows
hazeliscoding Sep 25, 2026
43f83eb
docs(roadmap): record the merged-window speedup
hazeliscoding Sep 25, 2026
914dacb
fix(sweep): re-send timed-out search pages at half the size
hazeliscoding Sep 25, 2026
95c2d5e
fix(sweep): treat a cut-off response body like a timeout
hazeliscoding Sep 25, 2026
cecc163
chore(debug): log why and how long each GitHub retry waits
hazeliscoding Sep 25, 2026
66d5c25
docs(roadmap): record the page-size fallback results
hazeliscoding Sep 25, 2026
18aeb14
perf(sweep): reuse a shrunken page size per kind of search for an hour
hazeliscoding Sep 25, 2026
dcc7e8a
revert: reuse a shrunken page size per kind of search for an hour
hazeliscoding Sep 25, 2026
7f35ba7
docs(roadmap): record why whole-org views stay at about 110 s
hazeliscoding Sep 25, 2026
b92f733
docs(agents): open release pull requests as drafts early
hazeliscoding Sep 26, 2026
0bcf266
docs(roadmap): no Tauri rewrite before 1.0; list the port under Later
hazeliscoding Sep 26, 2026
e1fc4e8
feat(sweep): carried-over PRs, team review requests, and a detail que…
hazeliscoding Sep 26, 2026
29d7e11
docs(roadmap): record the step 5 data measurements
hazeliscoding Sep 26, 2026
c53c4e8
feat(attention): judge every open PR after each sweep
hazeliscoding Sep 27, 2026
21a58d1
feat(board): Sweep section with each PR's reason, age and next step
hazeliscoding Sep 27, 2026
a7da585
feat(board): snooze Sweep rows until they change, get worse, or the d…
hazeliscoding Sep 27, 2026
be228e6
feat(tray): team attention line, tooltip fallback, and the Sweep in S…
hazeliscoding Sep 27, 2026
c221643
feat(attention): quiet rows keep the Sweep short
hazeliscoding Sep 27, 2026
0b04cb0
docs(readme): describe the Sweep and the v0.11 search changes
hazeliscoding Sep 27, 2026
2215a83
ci(release): publish hand-written notes and refuse a tag without them
hazeliscoding Sep 28, 2026
abee35e
chore(release): v0.11.0
hazeliscoding Sep 28, 2026
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
23 changes: 21 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ name: Release
# AppImage, then publishes one GitHub release with all of them. latest.yml and
# latest-linux.yml (+ blockmaps) are what electron-updater reads to self-update
# installed copies — they must ship with every release.
#
# The release body is the hand-written docs/releases/<tag>.md. A tag without
# one fails before anything builds: every release says what changed.
on:
push:
tags: ['v*']
Expand All @@ -12,7 +15,20 @@ permissions:
contents: write

jobs:
notes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Release notes exist
run: |
f="docs/releases/${{ github.ref_name }}.md"
if [ ! -s "$f" ]; then
echo "::error file=$f::Missing $f. Every release needs hand-written notes (see AGENTS.md > Releases)."
exit 1
fi

build:
needs: notes
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -60,6 +76,9 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: docs/releases
- uses: actions/download-artifact@v4
with:
path: dist
Expand All @@ -70,5 +89,5 @@ jobs:
run: |
gh release create "${{ github.ref_name }}" dist/* \
--repo "${{ github.repository }}" \
--title "${{ github.ref_name }}" \
--generate-notes
--title "PR Sweep ${{ github.ref_name }}" \
--notes-file "docs/releases/${{ github.ref_name }}.md"
41 changes: 34 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,24 @@ organization. It ships for Windows and Linux.
- `statusCheckRollup` and `timelineItems` dominate sweep latency. Request an expensive field only
in the query whose rows display it (`QUERY_BARE`, `QUERY_OPEN`, `QUERY_QUEUE`). Measure sweep
time on a large org before and after adding fields.
- A field only some rows need (`mergeable`, review times) goes in `DETAIL_QUERY`, fetched with
`nodes(ids:)` for just those rows, never in a search. Details are optional: if that query
fails, the sweep still succeeds and those fields stay null.
- Verify API behavior against the live API before encoding it. Record each verified quirk in the
header comment of `github.service.ts` and in the README.

## Cached data and config

- The boot refresh and auto-refreshes patch `snapshot.json` incrementally, so a PR nobody touched
on GitHub keeps its cached row. When `PrRow` gains a field, old snapshots must get a full
re-sweep. v0.11 adds a snapshot schema version for this; bump it with every row change.
on GitHub keeps its cached row. When `PrRow` or `SweepResult` changes shape, bump
`SWEEP_SCHEMA` in `desktop/src/shared/types.ts`. Snapshots from another schema are then
never painted or patched, and the first refresh after an update is a full one.
- `ConfigService` migrates `config.json` on read (`normalizeProfile`). A new setting needs a
default there, must survive profile export/import, and needs a case in
`config.service.test.mjs`.
- Profile export never includes tokens or machine-level preferences.
- Sweep snoozes live in the renderer's `localStorage` (`prsweep-snoozes`), per machine, next to
the theme choice. They're never exported, and entries from earlier days are pruned.
- Tokens are encrypted at rest with Electron `safeStorage` in `token.bin`. Never log a full
token.
- Don't rename the userData folder (`app.setName('pr-sweep')`) or the executable
Expand All @@ -58,8 +64,10 @@ organization. It ships for Windows and Linux.

## Product rules

- The attention engine is the single definition of "needs attention". The Sweep section, the
sprint summary and the standup all read from it.
- The attention engine (`desktop/src/main/core/attention.ts`) is the single definition of
"needs attention". `prs:fetch` runs it over every open row after every sweep, cached rows
included. The Sweep section, the tray line and the sprint summary read its output; the
renderer never decides on its own whether a PR needs attention.
- Workflow health, not human performance: no per-person counts, leaderboards or review stats.
- No AI features before 1.0.
- Windows and Linux only. macOS is not planned.
Expand Down Expand Up @@ -92,13 +100,32 @@ Run these from the repo root unless noted.
- From `desktop/` after a build, `GH_TOKEN=$(gh auth token) node e2e/screenshot.mjs` writes
screenshots to `desktop/e2e/shots/`. `PRSWEEP_DEMO=1` points it at a public org for README
images.
- `PRSWEEP_DEBUG=1` makes the main process log GraphQL variables and response bodies.
- `PRSWEEP_DEBUG=1` makes the main process log GraphQL variables and response bodies, plus one
`[sweep]` line per sweep with its mode, duration, requests and retries.
- From `desktop/` after `npm run build:main`,
`GH_TOKEN=$(gh auth token) node e2e/bench-sweep.mjs <org> [login,login,…] [runs]` times full
sweeps and auto-refreshes against the live API. The performance budget in `ROADMAP.md` is
measured with it.

## Releases

- The app version lives in `desktop/package.json`. The root `package.json` version isn't used.
- Bump the version in the same commit as the change and end the subject with it:
`fix: single-instance lock … (v0.10.4)`.
- **Each roadmap release gets its own branch**, `release/vX.Y`, cut from `main`. All of that
milestone's commits go there. Open a draft pull request to `main` early, so CI runs on every
push, and keep its testing steps current. When the milestone's "Done when" holds, mark it
ready, merge, then tag `vX.Y.0` on `main`.
- A roadmap release bumps the version once, in the last commit on its branch:
`chore(release): v0.11.0`. A patch outside a milestone bumps it in the fix commit and ends the
subject with it: `fix: single-instance lock … (v0.10.4)`.
- **Every release has hand-written notes** in `docs/releases/vX.Y.Z.md`, committed with the
version bump. The release workflow publishes that file as the release body and fails before
building if it's missing. Write them for a reader skimming on a phone:
- Open with one bold **TL;DR:** line saying what changed and why it matters.
- Then short sections, in this order, only when they have something: `## ✨ New`,
`## ⚡ Faster`, `## 🐛 Fixed`, `## 👀 Heads up` (anything a user might trip over), and
`## ⬆️ Getting it`.
- One line per bullet, starting with a **bold** phrase. Plain words, user-visible effects,
real numbers when there are some. No commit hashes, no internals, no paragraphs.
- Pushing a `v*` tag runs `.github/workflows/release.yml`. It builds the signed Windows installer,
the portable exe and the Linux AppImage, then publishes one GitHub release. The `latest*.yml`
files and blockmaps must ship with every release, because the auto-updater reads them.
Expand Down
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ own dashboard shows what's waiting on *you*, not on the team. PR Sweep puts the
on one board, sorted by GitHub's own review state, so nobody maintains labels or a project board.
Set the org, the team and the sprint's dates once. It refreshes every five minutes from the tray.

> **Status:** v0.10, in daily use. Windows and Linux builds are on [Releases](../../releases) and
> update themselves. Next is a Sweep list that says why each stuck PR needs attention and what to
> do about it. See [ROADMAP.md](ROADMAP.md).
> **Status:** v0.11, in daily use. Windows and Linux builds are on [Releases](../../releases) and
> update themselves. Next is a sprint summary with a standup you can paste into chat. See
> [ROADMAP.md](ROADMAP.md).

<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/board-dark.png">
Expand Down Expand Up @@ -60,14 +60,22 @@ Grab a build from [Releases](../../releases).

## What it does

- **Sorts** every PR your team has open or merged in the date range into Needs review, Changes
requested, Approved and Merged, from GitHub's `reviewDecision`. There are no labels to keep up.
- **Sweeps** the team's open PRs for the ones that need a human: failing CI, merge conflicts,
feedback nobody has addressed, pushes waiting on a re-review, approvals nobody merged, and PRs
nobody was asked to review. Each row says how long it has been that way, with a one-click next
step. PRs that are only waiting or stale, or untouched for a month, sit behind a toggle, and
you can snooze a row until it changes or tomorrow. In a sprint's last two days, it also says how
many open PRs aren't approved yet.
- **Sorts** every open PR your team has, plus what merged in the date range, into Needs review,
Changes requested, Approved and Merged, from GitHub's `reviewDecision`. There are no labels to
keep up.
- **Queues** the open PRs anywhere in the org that are waiting on *your* review, with how long
each has waited.
- **Flags** failing CI on every open PR, and PRs untouched for longer than a threshold you set.
Drafts stay hidden unless you show them.
- **Notifies** from the tray when a PR lands in your queue, or when one of yours is approved, gets
changes requested or starts failing CI. Closing the window keeps it watching.
changes requested or starts failing CI. The tray menu also counts what the Sweep has for the
team. Closing the window keeps it watching.
- **Shares** a setup. Save org, team and date-range profiles, then export them as JSON for
teammates to import. Tokens are never exported.
- **Opens instantly.** The last sweep is cached on disk, so the board appears at once and
Expand Down Expand Up @@ -113,6 +121,12 @@ GitHub search has quirks, all checked against the live API. PR Sweep works aroun
reliable check is whether the org's repositories are visible at all.
- Search returns at most 1000 results per query, however you paginate. When a busy range would
pass that, PR Sweep splits the date window in half and searches the halves.
- Paging through one long search is slow, so merged PRs are searched a week at a time, four
weeks in parallel.
- Heavy searches can time out, as a 502 or as a 200 with a cut-off body. Those pages are re-sent
at half the size, down to 25 rows, instead of being repeated as-is.
- Mergeability and review times are too costly to ask for on every searched PR. They come from
one follow-up query, only for the PRs whose attention depends on them.
- Auto-refreshes ask only for PRs updated since the last sweep and patch the cached result. A
manual Refresh always sweeps in full.

Expand Down
Loading
Loading