Brief
What had to change
"Every career tool I wanted to hand an Indonesian friend was in English, priced in dollars, and shaped around applying to a US company. A few had an Indonesian UI bolted on and you could feel where the bolt was. They still tell you to take the photo off your CV — normal advice in the US, the opposite of the convention here. None of them have heard of CPNS. None of them know which papers have to be in your hand before you can legally start work in Japan." And the design response, in the same voice: "Translating the buttons doesn't fix that, because the assumptions live in the data, not the copy. So I built it the other way round: model one hiring market properly, and treat other languages as a rendering problem."
Approach
How the work was shaped
He builds the harness before the feature: a `Stop` hook gates every agent turn with lint + typecheck on the dirty tree and exits 2 so the failure text feeds straight back to the model, a `/ship` skill encodes the release order and refuses a blanket `git add -A`, and the vendored `audit-bp` reviewer is required to pull live Next.js / React / Convex docs before it will call anything a violation, on the stated grounds that “anything your memory says may be stale”. That instinct paid off in the 2026-07-30 deployment audit, which found the pre-push hook had only ever resolved the legacy self-hosted backend and warned about nothing — so `convex/**` changes, including security fixes, never reached production while the hook reported success; it now resolves a target in three tiers, prints which one it hit, and aborts outright on a non-prod target. The clearest judgement call is one where he rejected his own fix: re-running category inference across the job board would have rewritten 1,563 of 2,809 listings and collapsed 72% of the board into engineering, off a tag threshold he had no labelled data to validate, so he shipped only the rows the job title decides and deliberately left WeWorkRemotely listings alone because that feed carries the source's own category and overwriting it with inference would be a downgrade.
Outcome
What the result changed
320 of 2,809 job listings reclassified, idempotent on re-run
Constraints that shaped the system.
- 01
Indonesia-first by design, not by backlog: "I'd rather be exactly right for one market than mediocre everywhere."
Constraint - 02
The multi-language layer is deliberately the cheap version — one tap, 12 languages, driven by the Google Translate cookie, not a hand-written message catalog. Owner's words: "It's the cheap version and I'll say so." The ~90KB Google script only loads if someone asks for it.
Constraint - 03
Free with no business model behind it: "No pricing page, no card field, no billing code in the repo. I never built a paywall, so there isn't one sitting there waiting to be switched on."
Constraint - 04
"It will not get you a job." The terms say so in Indonesian ("Tidak ada jaminan bahwa Anda akan mendapat pekerjaan") and the ATS score card carries "Pakai sebagai panduan, bukan jaminan."
Constraint - 05
The salary and cost-of-living figures are "a static reference dataset I compiled, not a live market feed."
Constraint - 06
AI runs on gpt-4o-mini through an OpenRouter key the owner pays for, capped at 10 requests/minute and 100/day per person under a shared 300/hour ceiling (guest sessions shed first, at 150). Bring-your-own-key routes to your key but the caps still apply — "they're app-level buckets, not a billing guard."
Constraint - 07
Licence is PolyForm Noncommercial 1.0.0 — source-available, not OSI open source. Owner's words: "I'd rather be precise than collect the badge."
Constraint - 08
One person, no team: "nobody to escalate your bug to but me."
Constraint - 09
Demo accounts are cascade-deleted by a nightly cron once they are 7 days old; converting to an email account keeps everything.
Constraint - 10
The ATS scorer is deliberately deterministic TypeScript with no AI in it, and shows its five-part breakdown (keyword coverage, hard skills, experience fit, section completeness, parseability).
Constraint
