Higher-order code review

Review the change, not the diff.

Agents write more code than anyone can read, so we need abstractions to stay in control.

Works with
  • Python
  • Go
  • TypeScript
  • JavaScript
  • Java
  • Svelte
  • React

Big diff, small change.

Abstraction decomposes a pull request into the changes that matter.

PR #4821 12 files +512 −131
billing/prorate.go
41 41 func ProrateSeats(sub Subscription, prev, next int) Charge {
42 - days := plan.CycleDays
42 + days := sub.CycleDays()
43 + delta := seatDelta(prev, next)
43 - if next < prev {
44 - return Charge{}
45 - }
44 + if delta == 0 {
45 + return Charge{}
46 + }
47 + remaining := sub.DaysLeft(now)
46 - amount := unit * (next - prev)
48 + amount := unit * delta * remaining / days
49 + seats := float64(delta*remaining) / float64(days)
47 - return Charge{Amount: amount, Seats: next - prev}
50 + return Charge{Amount: amount, Seats: seats}
48 51 }
52 +
53 + func seatDelta(prev, next int) int {
54 + d := next - prev
55 + if d < 0 {
56 + d = 0
57 + }
58 + return d
59 + }
billing/seats_test.go
12 12 func TestProrateSeats_MidCycle(t *testing.T) {
13 - sub := Subscription{Plan: monthly}
13 + sub := Subscription{Plan: monthly, StartedAt: now.AddDate(0, 0, -12)}
14 14 got := ProrateSeats(sub, 3, 5)
15 - want := unit * 2
15 + want := unit * 2 * 18 / 30
16 16 if got.Amount != want {
17 17 t.Fatalf("got %d, want %d", got.Amount, want)
18 18 }
19 19 }
20 +
21 + func TestSeatDelta_Downgrade(t *testing.T) {
22 + if d := seatDelta(5, 3); d != 0 {
23 + t.Fatalf("want 0, got %v", d)
24 + }
25 + }
billing/pb/seats.pb.go
1 1 // Code generated by protoc-gen-go. DO NOT EDIT.
2 - // protoc-gen-go v1.34.1
2 + // protoc-gen-go v1.36.2
3 3 type SeatChange struct {
4 - state protoimpl.MessageState
5 - sizeCache protoimpl.SizeCache
6 - unknownFields protoimpl.UnknownFields
7 -
8 - SeatCount int32 `protobuf:"varint,3,opt,name=seat_count,proto3"`
4 + state protoimpl.MessageState `protogen:"open.v1"`
5 + SeatCount int32 `protobuf:"varint,3,opt,name=seat_count,proto3"`
6 + unknownFields protoimpl.UnknownFields
7 + sizeCache protoimpl.SizeCache
9 8 }
10 9 func (x *SeatChange) GetSeatCount() int32 {
11 10 if x != nil {
12 11 return x.SeatCount
13 12 }
14 13 return 0
15 14 }
16 - var file_billing_seats_proto_rawDesc = []byte{
17 - 0x0a, 0x13, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x65,
18 - 0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x62,
15 + const file_billing_seats_proto_rawDesc = "" +
16 + "\n\x13billing/seats.proto\x12\abilling\"+\n" +
17 + "\nSeatChange\x12\x1d\n\nseat_count\x18\x03 \x01(\x05R\tseatCount"
593 more lines across 9 more files
Semantic diff 2 functions changed
ProrateSeats billing/prorate.go +9 −6

Prorates to the day using the subscription's cycle length, and now returns fractional seats.

41
func ProrateSeats(sub Subscription, prev, next int) Charge {
42
days := plan.CycleDayssub.CycleDays()
⋯ 5 lines
48
amount := unit * (next - prev)delta * remaining / days
49
seats := float64(delta*remaining) / float64(days)
seatDelta new billing/prorate.go +7

Returns how many seats were added. Downgrades clamp to zero.

10 files set aside: regenerated protobuf stubs and a rename. Tests are shown separately.

Small diff, big impact.

Abstraction follows a change into everything that calls it.

PR #4790 1 file +1 −1
accounts/subscription.ts
⋯ 40 unchanged lines
41 41 export function isActive(sub: Subscription): boolean {
42 - return sub.status === 'active';
42 + return sub.status === 'active' || sub.status === 'past_due';
43 43 }
⋯ 52 unchanged lines
That's the whole pull request.
Callers outside accounts 8 callers
  • lifecycle-emails sendPaymentReminders

    Payment reminders stop.

  • seats billSeatChanges

    Seats get billed to a declined card.

  • usage-export countPayingAccounts

    Past-due accounts count as paying.

  • entitlements canUsePremium

    Premium stays on after the card fails.

  • seats canAddSeat

    Past-due accounts can still add seats.

  • api-gateway rateLimitFor

    Unpaid accounts keep paid rate limits.

  • usage-export churnedAccounts

    Churn misses accounts that stopped paying.

  • lifecycle-emails sendWinBackOffer

    Win-back offers skip past-due accounts.

None of them appear in the diff.

Catch bugs at every level of abstraction.

The small ones are precise; the expensive ones take reasoning.

In the function
reserveSeat race condition

Checks capacity and inserts in two queries. Two sign-ups at once overbook.

applyCredit nested transaction

Opens its own transaction inside closeInvoice's. A rollback leaves the credit applied.

Across its callers
findAccount 3 callers

Now returns nil for closed accounts. All three callers dereference it unchecked.

taxRate 1 call away

Now returns 20 instead of 0.2. Checkout still multiplies the total by it.

Across the business logic
refundOrder revenue

Skips the ledger, so revenue reports keep counting refunded orders.

removeMember access

Frees the seat but not the member's API tokens, so they keep access.

Left out of the default review: formatting, naming and style. Your linter covers those, and a check can cover house rules.

A review you'll tune up, not turn down.

Every codebase is different. With Abstraction, your reviews are customized to your team's needs.

01 · Ask our agent
Agent billing
Make sure personal data never reaches a log line.
Tracing personal data to the loggers 2

Personal data reaches a log line in one place today. I drafted a check for it.

02 · Audit
No PII in logs New 1 error
Result
  1. api/invites.ts email Email
  2. → mail/queue.ts job.payload
  3. → log/logger.ts info(job) Logger
Create check
03 · Every PR
Some checks were not successful 1 failing and 9 successful checks
No PII in logs Required
  • Entrypoints reach an auth check
  • Money is never a float
  • Jobs are dispatched via the queue
  • Refunds go through the ledger
  • Webhooks verify their signature
  • No secrets in logs
  • Database access goes through repositories
  • Public responses use the shared envelope
  • Tests stay isolated

Each check is its own GitHub check run, so branch protection can require it.

Tags, checks and templates

A reviewer you can talk to.

Discuss findings with our agent at any time.

Platform

The comprehension layer for your codebase.

The semantic model behind every review also answers your team's questions and documents your system.

In our evals, a coding agent with Abstraction answered questions about code structure several times faster, at a fraction of the cost. How we measured it

Frequently Asked Questions

Learn more about Abstraction and how it works.

Review your next pull request with Abstraction.

We are onboarding teams in waves. Request access and we will reach out when your spot opens.

How review works

ABSTRACTION

2026

Abstr AB · Karlavägen 50, Stockholm

About Terms of service

contact@abstraction.com

Enter email to join our waitlist!