Work

kova-ops

the automation layer

Four systems that run Kova without me watching: the safety hooks that keep an AI agent off the live store, the books, the delivery routing and the content pipeline. Every line of code is Claude Code’s, written to my spec; the decisions, the tests and the running of it are mine. A revenue operations or GTM engineering seat does this work for a company; I did it for a business of one.

I specced and tested everything in amber. The rest are systems Kova already runs on.

agentclaude · mcp
guards97 tests
What the 97 tests check

Two suites, run together, built against real throwaway repositories — one dirty, one clean — rather than mocks, because half of what these hooks decide depends on whether there is anything to lose.

  • 20theme-push-guard · must ask. Anything that reaches production: a push to main in seven spellings, a merge on main, shopify theme push, and gh pr merge in four forms — including when a compound command cds into the theme first.
  • 14theme-push-guard · must pass silently. Ordinary work: branch pushes, git status, git commit, shopify theme dev, a push in the docs repo, and grep -rn 'push'. A guard that cries wolf gets routed around.
  • 5theme-push-guard · permission mode. ask in every mode a human can answer; deny under bypassPermissions, where a prompt reaches nobody and the run would hang on a question no one sees.
  • 13theme-push-guard · the second deploy-connected repo. The guard used to key on one directory name. A repo can be a directory called site and only the origin remote says which site, so it keys on the basename or the remote slug — matching on basename alone would guard a repo nobody asked for. Six assert main still asks; seven assert the preview branch stays silent, because shipping there is the intended workflow rather than a risk.
  • 7theme-push-guard · heredoc tokenising. shlex does not know what a heredoc is, so python3 - <<'PY' handed every word of the body to the tokeniser as an ordinary argument — which blocked a legitimate push whose only sin was naming a guarded repo in prose inside a doc-writing heredoc. Bodies are stripped before tokenising; the push itself lives outside the body and is still parsed normally.
  • 7git-destructive-guard · history. Always ask, even on a clean repo: reset --hard <ref>, branch -D, stash drop, stash clear, force push.
  • 8git-destructive-guard · working tree. Ask only when something is at risk. The same command is a question on a dirty repo and silence on a clean one, which is why the suite needs real repos.
  • 9git-destructive-guard · ordinary work. Must pass silently — including git clean -n, a dry run, and git branch -d, the safe delete.
  • 5git-destructive-guard · the cd blind spot. The hook payload's cwd is where the shell was BEFORE the command runs, so cd <other repo> && git clean -fd was judged against the wrong repo and waved through. Found by probing, not by reading.
  • 5git-destructive-guard · permission mode. The same ask/deny split.
  • 4both guards · the exit contract. The one that matters most. A deny is exit 2 on stderr, not advisory JSON on stdout — so it stops the call instead of being printed and ignored. Two cases per guard assert the deny path writes nothing to stdout and the ask path stays exit 0.
  • Shopifyorders
  • Squarepayments
  • Xerobooks
  • Notioncanon
  • Gmailcomms
  • Cloudflaredeploy

Two hooks stand between the agent and anything it can break: a deploy reaching the live storefront, and a destructive git command.

What it taught me: I found three production bugs in these hooks by probing them with crafted payloads. Reading the code said they were fine.

matcherrules + dates
review gatehuman present
What one sitting does

The mechanical tier only, and I sit with it. Everything below is a rule. The moment it takes judgment, the gate hands it back.

  • 1Read. Square card takings, Shopify payouts, and six Xero bank accounts.
  • 2Match on rules and dates. Internal transfers, Shopify invoice matches, rule-driven deposits.
  • 3Stop at expense coding. Business-versus-personal is a tax position rather than a rule, so a human answers it. This is the boundary, not a gap.
  • 4Result. 95 unreconciled items to zero, all six accounts green, receivables $656.65 to $0.00.
  • Xero6 accounts green
  • receivables$656.65 → $0
  • expense codingstays mine

One sitting: 95 unreconciled items to zero, all six bank accounts green, receivables $656.65 → $0.00. Expense coding is deliberately NOT automated — business-vs-personal is a tax position, so the gate stops there and a human answers it.

What it taught me: where to stop. Expense coding is not automated, because business-versus-personal is a tax position and not a rule, so the gate hands it back to a human.

orderslive, unfulfilled
routertraffic-aware
How a delivery run gets built

Live orders in, one map link out. Two of these four steps exist only because a plausible shortcut was measured and failed.

  • 1Pull. Live unfulfilled Shopify orders, addresses as written.
  • 2Find the departure time by binary search. The latest you can leave and still make every stop inside the hard 9pm cap.
  • 3Check the freezer against capacity before anything is loaded, and make the defer-or-deliver call on a late order.
  • 4Emit one Google Maps link with every stop in order. A ZIP-centroid fallback was tested for the geocoding and rejected: four miles off.
  • 5Hand it to an agent. The same solver runs as an MCP server with two schema-declared tools, solve_route and latest_departure, so an agent can plan the run instead of a person reading a terminal. 23 tests drive it over the protocol itself; six of them are malformed calls the server has to survive.
  • Mapsone-tap run
  • freezer checkcapacity
  • defer calllate orders

Orders in, an ordered run and a single map link out. The departure time is found by binary search — the latest you can leave and still make every stop. A ZIP-centroid fallback was tested and rejected: four miles off.

What it taught me: the ZIP-centroid fallback looked reasonable and was four miles wrong. It only failed once I measured it against real addresses.

raw footage46 shoots
autocuttranscript → cuts
The three stages

A library of 46 raw shoots and 46 finals, each with a textless master, so a cut can be re-subtitled or re-hooked without going back to the footage.

  • 1Transcribe. The spoken take becomes an SRT with timings.
  • 2Derive the cut list. Offsets differ from month to month, so the timing is measured per shoot rather than assumed — that assumption is what used to put every cut a beat late.
  • 3Assemble. One keystroke, and the result is a rough cut plus a textless master.
  • textless masters46 finals
  • Notioncalendar

A three-stage pipeline: transcribe, build the cut list from the transcript, assemble. It runs against a library of 46 finished pieces with clean textless masters, which is what makes re-cutting old footage cheaper than shooting new.

What it taught me: re-cutting the library is cheaper than shooting new, but only because I kept the masters textless months earlier.

built with claude code · mcp · cloudflare workers · python

GitHub

Open to work JET/OS · code