Loading...

Skip to main content

How I shipped production code in a language I don't know

· 6 min read
Gábor Kiszler
Engineer at Aliz

I shipped a production application in TypeScript — a language I've barely used — with a Node/Fastify backend, a React frontend, Firestore for data, and Terraform-managed infrastructure on GCP. I worked mostly from a terminal rather than a full IDE, and the frontend was something I'd essentially never done. On paper that should have produced confident nonsense. It didn't, and the reason wasn't talent or luck. It was the workflow.

For a few weeks I worked strictly spec-first, and the loop had a fixed shape:

Plan → Propose → Codex review → Apply → Review → Archive

I planned and discussed the change; OpenSpec turned that into the artifacts (proposal, design, spec, task list), so my real work was the thinking and the option-weighing, not the boilerplate; an adversarial reviewer (Codex) tore into the proposal; I implemented it behind failing tests; the implementation got reviewed again; and finally the change was archived and its spec folded into the living docs. A lot of the code itself was written by an AI agent, which makes the guardrails the whole story.

Because the app was greenfield, the big-picture plan — the milestones — was drawn up front, before any change. I worked through them in order; then, as the system took shape and the real decisions started surfacing, I spun up OpenSpec changes on the fly, one per decision. So planning was two-layered: a stable milestone map set at the start, and a stream of small specs filling it in as I learned what the thing actually needed.

Here's the honest version — what genuinely helped, and what was ceremony I had to decide was worth it.

What actually worked​

  • Deciding before coding. The hard questions surfaced before a line of code existed — the edge cases, "what happens when the external thing is down," "where should this setting live." OpenSpec did the actual authoring: it turned an intent I described into the proposal, design, spec, and tasks, so what was left to me was the part that should be human anyway — planning the change and discussing the options. Most of those are product decisions wearing technical clothes, and writing them down meant I decided them on purpose. When I got them wrong, I got them wrong on paper, which is cheap.
  • The adversarial review earned its keep. After every proposal and every implementation, something reviewed the change with one job: find what's wrong. I expected noise. Instead it kept catching the boring, plausible mistakes — the kind that pass the tests, pass a human skim, and confuse someone in production three weeks later. A reviewer that's never tired, never invested, and pessimistic by design raised the floor on everything I merged.
  • The spec stayed true. Because it was the source of truth and got reconciled with the code at the end of each change, the description of how the system behaves lived right next to the system. When I later updated the user docs, the spec was the thing I trusted.
  • A running design-decisions log. Alongside the per-change specs, I kept one separate file for the important reasoning — why this approach over that one, which trade-off we accepted and why. Specs capture what the system does; this captured why, and it outlived any single change. Months on, "why is it built this way?" has an answer that isn't a guess or a git blame archaeology dig.
tip

The best mental model I found: a spec is where your thinking lives, independent of whether you know the language's idioms. That's exactly what let me work in a stack I didn't know.

Where it got in the way (or didn't save me)​

  • Specs encode intent, not reality. The humbling one. A spec makes your intentions rigorous; it does nothing for your assumptions about the outside world. At one point everyone — me and the tools — shared a confident belief about how an external system behaved. The spec was clean, the logic was tested against that belief, and the belief was wrong. No review and no test-against-a-mock could catch it, because it was a fact about someone else's system. The only thing that caught it was stopping the argument and measuring.
  • The ceremony isn't free. For a real change, the full loop is clearly worth it. For a one-line fix, a proposal + design + spec + task list is heavier than the work. I kept having to judge "real change or just a fix?" and didn't always get it right.
  • Requirements still move. No amount of upfront spec stopped me from framing something the wrong way and re-cutting it later. What changed wasn't the number of iterations — it was their legibility: a clean, self-contained correction instead of a tangle of half-related edits.
note

My rule now: the process should scale with blast radius, not line count. A three-line change that alters behaviour for everyone deserves the whole loop; a chunky refactor that changes nothing observable doesn't.

The part I didn't expect: it let me work far outside my comfort zone — safely​

Unfamiliar language, thin toolchain, first-time frontend, an AI generating a lot of the code. Any one of those, unscaffolded, is a recipe for shipping things you don't understand. What made it trustworthy was precisely the structure around it — a spec the code had to satisfy, tests with real coverage gates, and an independent skeptic before anything landed. The guardrails stood in for the expertise and the IDE I didn't have. OpenSpec drafted the specs, the agent wrote most of the code, the tests covered the fluency I lacked — and what that left for me was planning, weighing options, and making the calls the machine shouldn't.

And I'll be honest about the double edge: this same setup lets you ship code you couldn't have written yourself. That's powerful and slightly dangerous, and the gates are the only reason it lands on "powerful." My role didn't shrink — it concentrated on the decisions that were actually mine: the trade-offs, ship-now-or-wait, is-this-even-worth-doing.

Would I do it again?​

Yes — with a lighter touch for small things. The magic wasn't any single tool. It was three habits: decide on paper, verify your assumptions against reality, and let something adversarial check your work before it ships. That's what let someone operate confidently in a language they don't know, a stripped-down toolchain, and an unfamiliar part of the stack without it becoming reckless. The spec gave me somewhere to think; reality reminded me that thinking isn't measuring; the skeptic kept me honest. For anything with real blast radius, I'll take that trade every time.

Further Reading​