A native macOS app for comparing two to four patches against one code base, side by side, so a reviewer can pick the best patch for a pull request and write down why.
Each patch gets a column. Select some lines in one column, and the app
finds the same code in the others and tells you how it compares:
exact, modified, moved, or absent. Make any column the
golden patch and read the others as diffs against it. Record findings
as you go; each one carries a reference such as main.rs L94:L101,
generated from your selection, never typed. Export the review as
Markdown, ready to paste into a review form, or as JSON for tools.
The name is a pun on Patch Adams, the clown doctor.
- Install
- Quick start
- The window
- Reviewing
- Sessions and export
- Keyboard
- Settings
- Build from source
- Project layout
- Further documentation
Requires macOS 14 or later.
Download the DMG, open it, and drag Patch Adams to Applications. To build the DMG yourself, see Build from source.
git is optional. With it, the app can read a base straight from a
git revision. Without it, directory and archive bases still work; the
welcome screen shows which you have.
- Choose the base, the code the patches were made against: a
directory, a
.tar/.tar.gz/.ziparchive, or a git revision. No base at hand? Add the patches first, then click Rebuild one from the patches; every line any patch shows is used, the rest is filler. - Add two to four patches: drop
.patchor.difffiles on the window, click Add Patches…, or paste a diff with ⌘⌥V. - Read the request. Paste the task prompt into the request panel at the top. Findings are judged against it.
- Pick a file in the tree on the left. It opens in every column.
The app asks once whether to review blind (see Blind mode).
| Zone | What it does |
|---|---|
| Request panel | The task the patches answer. Open on a new session; ⌘R toggles it. |
| File tree | Every file any patch touches, with one status chip per patch: changed, added, deleted, unchanged, or failed to apply. Filter box; Tests Only (⌘⌥T). |
| Columns | One per patch. The header shows the label (A–D) and the patch file name, so a label can never be confused with the wrong file. Above each header, a slot: drop a patch file on it, or click it, to replace that column's patch. With fewer than four patches, an Add a patch slot sits to the right. |
| Gutter | Two line numbers per line: base on the left, post-patch on the right. Removed lines show inline, struck through; ⌘⌥− hides them. |
| Comparison panel | Below the columns. For the selected lines, one pane per other column with a word-level diff against the located lines. |
| Findings sidebar | On the right: the findings list with filters, the ranking, and the export buttons. |
| Status bar | The current file, golden-mode diff counts, blind mode, and the session timer. |
A file that fails to apply stays visible as its raw hunks with a warning. Files over 50,000 lines open in a reduced, hunks-only mode.
The app is built around the order a careful comparative review takes: read the request, scan the tree, review one file at a time across all columns, pick a provisional golden patch, record findings as you see them, and rank last.
Select lines in any column. Every other column marks where that code landed:
| Badge | Meaning |
|---|---|
exact |
The same text at the same place. |
modified |
The same place, different text. The percentage is the token similarity. |
moved |
The same text somewhere else in the file. |
absent |
No match above 60% similarity. A dashed red notice links to the patch's nearest hunk. |
When the selection declares something (a function, type, struct or const), the app looks for the same name first; a name match outranks a position match. Click a badge to make the located lines the new selection and compare back the other way.
If the app picked the wrong lines, select the right ones in that column and click Use selection as anchor in its comparison pane. The correction is saved with the session and wins on reload.
Set a column golden with ⇧⌘1–4, the toolbar picker, or the column menu. Every other column then shows its diff against the golden column's text instead of against the base, and the status bar counts the files and lines that differ. Press the same keys again to clear it.
- Search (⌘F) finds text in every file of every column, grouped by file, then column, each hit with its reference.
- Pin (⌘P) keeps a selection in the pins sidebar to come back to.
- Scroll lock (⌘L) keeps every column on the same base line.
]and[jump to the next or previous hunk in the focused column.
Select lines and press ⌘N. The sheet is prefilled with the column, the file and the reference. Fill in:
- Title, category and severity (trivial, minor, material, blocking): required. Categories start from a list of common patch problems (unnecessary helpers, excessive defensiveness, dead or unrelated code, and so on) and you can add your own.
- Does the request ask for this? yes, no, or unclear.
- Description, and a note for a correctness worry you want to park. Severity is about quality, not correctness.
- Not flagged: tick it to record that you considered something and passed. It shows in the export under its own heading.
Findings mark their lines in the column, coloured by severity. } and
{ walk the focused column's findings; clicking one in the sidebar
jumps every column there. Right-click a finding for Same Issue In to
copy it to another column at the matching lines, Copy Finding, or
Mark as Not Flagged.
In the findings sidebar, give each patch a rank from 1 to 4 (ties are allowed) and write the rationale.
⌘⌥B shows the patches as shuffled numbers and hides their file names, to reduce order bias. The shuffle is seeded and saved, so a session reopens to the same numbers. The export reveals which number was which patch only in its last section.
- Save (⌘S, ⇧⌘S for Save As) writes a
.patchadampackage: a folder holdingsession.jsonand a copy of every patch, plus a pointer to the base. Columns, golden choice, findings, pins, ranking, manual anchors and blind mode all come back on Open Session (⌘⌥O). If the base has moved, the app rebuilds one from the patches and says so. - Export Markdown (⌘E) writes the review write-up: the request (collapsed), a ranking table, findings grouped by patch in rank order, the not-flagged passes, and the rationale. ⌘⌥E copies it instead.
- Export JSON (⇧⌘E) writes the same record with every finding's anchors and confidence scores, for tooling.
- Copy Reference (⇧⌘C) copies
filename Lstart:Lendfor the selection. A selection of removed lines only givesfilename -Lstart:Lendin base line numbers.
The app never runs, builds or tests the patched code, and never uses the network. Nothing you load leaves the machine.
| Keys | Action |
|---|---|
| ⌘1 – ⌘4 | Focus column 1–4 |
| ⇧⌘1 – ⇧⌘4 | Set column 1–4 golden; again to clear |
| ⌘N | New finding from the selection |
} / { |
Next / previous finding in the focused column |
] / [ |
Next / previous hunk in the focused column |
| ⌘F | Search all columns |
| ⌘P | Pin the selection |
| ⌘L | Scroll lock |
| ⌘= / ⌘− / ⌘0 | Bigger / smaller / actual size code text |
| ⌘R | Request panel |
| ⌘⌥B | Blind mode |
| ⇧⌘C | Copy reference |
| ⌘S / ⇧⌘S | Save session / Save As |
| ⌘⌥O | Open session |
| ⌘E / ⇧⌘E / ⌘⌥E | Export Markdown / Export JSON / Copy Markdown |
| ⇧⌘N | New session |
| ⌘O / ⇧⌘O / ⌘⌥V | Open base / Add patches / Paste patch |
⌘, opens Settings: the code font (any fixed-pitch family) and size (9–24 pt; default system monospace at 12 pt), and whether to ask about blind mode on a new session. The View menu zooms the same size: Bigger (⌘=), Smaller (⌘−), Actual Size (⌘0).
Code is coloured by language, picked from the file extension (Go, Rust, Python, Swift, Zig, JavaScript and TypeScript, C and C++, shell, YAML, TOML, JSON, Markdown, CSS). The colours are the Spacetrucker Galactic palette: neon magenta keywords, cyan types, phosphor-green function names, peach strings, sunset-orange numbers and steel-blue comments, on deep Prussian blue in dark mode, and darker shades of the same hues in light mode. The added and removed backgrounds stay on top.
Requires Xcode 16 or later (Swift 6) and
XcodeGen for the app. The
engines build with swift alone and have no third-party dependencies.
git clone git@github.com:SpaceTrucker2196/PatchAdams.git
cd PatchAdams
make test # engine tests: swift test --enable-code-coverage
make build # the app: xcodegen + xcodebuild, warnings are errors
make harness # parse every fixture patch and apply it to its base
make dmg # a Release build in a DMG (ad-hoc signed by default)
The debug app lands at
.build/DerivedData/Build/Products/Debug/PatchAdams.app. To try it
without your own patches, use Debug → Open Demo Session (⌘⌥D):
four Go patches on a base rebuilt from them.
Pushing a tag with "Release" in its name builds the DMG on GitHub and attaches it to that release:
git tag v0.6.0-Release
git push origin v0.6.0-Release
A notarized DMG needs a Developer ID Application certificate:
make dmg notarize SIGN='Developer ID Application: river.io llc (U3Z59VXPUB)'
patchadam, the command-line harness, runs the engines without the
app:
swift run patchadam parse Fixtures/patches/old-fixture-8.patch
swift run patchadam hunks Fixtures/patches/old-fixture-8.patch
swift run patchadam apply Fixtures/bases/old-fixture-8 Fixtures/patches/old-fixture-8.patch
swift run patchadam diff old.txt new.txt
Design/ the app icon: owner artwork and the 1024 px icon cut from it
App/ SwiftUI shell over AppKit text views (project.yml → PatchAdams.xcodeproj)
Sources/
PatchModel/ strict unified-diff parser
BaseLoader/ read-only base: directory, archive, git revision
ApplyEngine/ in-memory apply with fuzz, line maps, references
DiffEngine/ Myers and patience line diff, token diff
AlignmentEngine/ locate a region in another column; golden rendering
SessionModel/ findings, ranking, session package, Markdown and JSON export
SyntaxHighlight/ per-language token scanner for syntax colour
patchadam/ command-line harness
Tests/ one XCTest target per engine
Fixtures/patches/ 10 invented fixture patches (tools/fixtures/make_fixtures.py); Fixtures/bases/ their rebuilt bases
Engines never import SwiftUI or AppKit, so swift test checks all of
them without the app. The app shell owns the session state and never
calls an engine directly.
| Document | What is in it |
|---|---|
docs/spec.md |
The product spec: requirements FR-1 to FR-35, architecture, the alignment algorithm |
MISSION.md |
Charter and the invariants the app must keep |
ROADMAP.md |
Milestones M0–M5 and the gates each one passed |
FACTORY.md |
Build and release runbook, and driving the app from a shell |
DECISIONS.md |
Decisions and refusals, with the reasons |
AGENTS.md |
Rules for coding agents working in this repo |
docs/dark-factory.md |
How the repo runs: production orders as issues, /converge, the autonomy contract |
wiki/ |
Research notes, published with GitHub Pages |