<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ben Clews - Software Engineer</title><link>https://clews.id.au/</link><description>Recent content on Ben Clews - Software Engineer</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><lastBuildDate>Mon, 17 Aug 2026 10:00:00 +1000</lastBuildDate><atom:link href="https://clews.id.au/index.xml" rel="self" type="application/rss+xml"/><item><title>Dockerfile Linters Beyond Hadolint</title><link>https://clews.id.au/posts/dockerfile-linters-beyond-hadolint/</link><pubDate>Mon, 17 Aug 2026 10:00:00 +1000</pubDate><guid>https://clews.id.au/posts/dockerfile-linters-beyond-hadolint/</guid><description>&lt;p&gt;&lt;code&gt;droast&lt;/code&gt; got a passing mention in a recent conversation: it is a Rust Dockerfile
linter that prints snarky remarks alongside its findings. I went to look it up
expecting a five-minute note, and instead lost an evening to it, because the
comparison chart it leads with says &lt;strong&gt;3.90× FASTER&lt;/strong&gt; in large type, and I
couldn&amp;rsquo;t work out why anyone would care&amp;hellip;&lt;/p&gt;
&lt;p&gt;Linting a Dockerfile doesn&amp;rsquo;t take long enough to have a throughput problem. So
either the number was measuring something that doesn&amp;rsquo;t matter in practice, or I
was missing something. I suspected the latter, so went digging.&lt;/p&gt;</description></item><item><title>Why the Patient Doesn't Know the Answer</title><link>https://clews.id.au/posts/why-the-patient-doesnt-know-the-answer/</link><pubDate>Tue, 21 Jul 2026 10:00:00 +1000</pubDate><guid>https://clews.id.au/posts/why-the-patient-doesnt-know-the-answer/</guid><description>&lt;p&gt;This is the third and last post in a short series about
&lt;a href="https://clews.id.au/posts/building-a-healthcare-app-with-ai-agents/"&gt;building an AI pharmacy simulator&lt;/a&gt;.
In the &lt;a href="https://clews.id.au/posts/why-the-model-doesnt-decide-the-medicine/"&gt;previous post&lt;/a&gt; I wrote
about keeping the clinical facts out of the model and in a file a pharmacist
controls. This one is about the other decision the design leans on, which is
splitting the patient and the examiner into two separate prompts.&lt;/p&gt;
&lt;p&gt;As with the rest of this series, I&amp;rsquo;m writing as a practitioner rather than a
security or assessment expert, which matters more here than in the other posts,
because this one strays into territory with real adversaries and a real research
literature. Where I make a claim about how these systems fail, I&amp;rsquo;ve tried to
anchor it to people who study that for a living.&lt;/p&gt;</description></item><item><title>Why the Model Doesn't Decide the Medicine</title><link>https://clews.id.au/posts/why-the-model-doesnt-decide-the-medicine/</link><pubDate>Tue, 14 Jul 2026 10:00:00 +1000</pubDate><guid>https://clews.id.au/posts/why-the-model-doesnt-decide-the-medicine/</guid><description>&lt;p&gt;Earlier I wrote about
&lt;a href="https://clews.id.au/posts/building-a-healthcare-app-with-ai-agents/"&gt;building an AI pharmacy simulator by directing agents&lt;/a&gt;,
and I promised to come back to the two design decisions that make it work. This
is the first of them. It took me the longest to get right, and it&amp;rsquo;s the one I&amp;rsquo;d
reach for again on any project where being wrong costs something.&lt;/p&gt;
&lt;p&gt;Same disclaimer as last time, briefly: I&amp;rsquo;m a software engineer, not a
pharmacist, and the clinical side of this leans on my wife, who teaches pharmacy
practice. I&amp;rsquo;m still fairly new to building with language models. So where I lean
on a claim about how these models behave, or about the clinical facts, I&amp;rsquo;ve
tried to point at something firmer than my own say-so.&lt;/p&gt;</description></item><item><title>What I Learned Building an App by Directing AI Agents</title><link>https://clews.id.au/posts/building-a-healthcare-app-with-ai-agents/</link><pubDate>Tue, 07 Jul 2026 10:00:00 +1000</pubDate><guid>https://clews.id.au/posts/building-a-healthcare-app-with-ai-agents/</guid><description>&lt;p&gt;My wife is a pharmacist who teaches pharmacy practice at our local university,
which is how I ended up here, building an AI patient simulator demo.&lt;/p&gt;
&lt;p&gt;It started around 2023, when ChatGPT took off and a lot of academic staff were
rattled by how easily it let students cheat, a concern that was reflected in the
&lt;a href="https://www.tandfonline.com/doi/full/10.1080/14703297.2023.2190148"&gt;academic literature of that year&lt;/a&gt;.
My wife and I chatted about it and our prevailing view was that trying to block
or limit these tools was a losing game, and the more useful move was to
integrate LLM tools into the teaching. One idea we landed on was a prompt a
student could paste into a chatbot during a lab or tutorial, so the model would
role-play a patient and the student could practise a counselling conversation on
the spot. It worked remarkably well. Students liked it, and my wife has gone on
to write a whole set of these role-play prompts.&lt;/p&gt;</description></item><item><title>Creating Patches in a Colocated Jujutsu Repo</title><link>https://clews.id.au/til/creating-patches-in-colocated-jj/</link><pubDate>Wed, 27 May 2026 11:30:00 +1000</pubDate><guid>https://clews.id.au/til/creating-patches-in-colocated-jj/</guid><description>&lt;p&gt;Today I needed to share a couple of commits as a patch with someone outside the
repo, and learned that &lt;code&gt;jj&lt;/code&gt; covers half the job. The other half is &lt;code&gt;git&lt;/code&gt;, right
there in the colocated repo.&lt;/p&gt;
&lt;p&gt;Say the graph looks like this and I want &lt;code&gt;wmktltov&lt;/code&gt; and &lt;code&gt;lzxnnyks&lt;/code&gt; combined into
one patch:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@ nomryxuq dev@example.com 2026-05-11 13:21:21 a63b6073
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ (no description set)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;○ lzxnnyks dev@example.com 2026-05-08 11:16:43 feature/api-backend bbea56a3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Complete API backend (TICKET-123 part 2)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;○ wmktltov dev@example.com 2026-05-08 10:47:51 f7db1946
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Begin API backend (TICKET-123 part 2)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="a-single-combined-patch-jj-diff-git"&gt;A single combined patch: jj diff &amp;ndash;git&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;jj diff --from &amp;#34;w-&amp;#34; --to &amp;#34;l&amp;#34; --git &amp;gt; backend.patch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;w-&lt;/code&gt; is &amp;ldquo;the parent of &lt;code&gt;w&lt;/code&gt;&amp;rdquo;: start &lt;em&gt;before&lt;/em&gt; &lt;code&gt;w&lt;/code&gt;&amp;rsquo;s changes and end at &lt;code&gt;l&lt;/code&gt;&amp;rsquo;s
content, producing one diff covering both commits&amp;rsquo; work. The &lt;code&gt;--git&lt;/code&gt; flag is
what makes the output applicable: jj&amp;rsquo;s default diff format is its own
human-readable &amp;ldquo;color-words&amp;rdquo; style, which &lt;code&gt;git apply&lt;/code&gt; and &lt;code&gt;patch&lt;/code&gt; can&amp;rsquo;t consume.
&lt;code&gt;--git&lt;/code&gt; switches it to standard git unified diff:&lt;/p&gt;</description></item><item><title>Jujutsu in colocated mode: where Git shows through</title><link>https://clews.id.au/posts/jj-colocated-mode/</link><pubDate>Wed, 27 May 2026 09:00:00 +1000</pubDate><guid>https://clews.id.au/posts/jj-colocated-mode/</guid><description>&lt;p&gt;&lt;a href="https://clews.id.au/posts/jj-less-friction-same-git/"&gt;In the last post&lt;/a&gt; I made the
case that you can adopt &lt;code&gt;jj&lt;/code&gt; without telling anyone, because it runs on top of
your existing Git repo in &lt;em&gt;colocated mode:&lt;/em&gt; &lt;code&gt;.git&lt;/code&gt; and &lt;code&gt;.jj&lt;/code&gt; side by side, push
and fetch still going through Git. Turns out, colocated mode has &lt;em&gt;seams&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;A seam is a moment where Git and &lt;code&gt;jj&lt;/code&gt; look at the same repository and describe
it differently: Git says the working tree is dirty while &lt;code&gt;jj&lt;/code&gt; says everything&amp;rsquo;s
committed, a routine fetch reports that it &amp;ldquo;abandoned&amp;rdquo; a commit, or &lt;code&gt;git am&lt;/code&gt;
refuses to run. Each one looks alarming the first time, but turns out to be
harmless once you understand what Git and &lt;code&gt;jj&lt;/code&gt; are each describing.&lt;/p&gt;</description></item><item><title>Recovering a Single File from Jujutsu's Operation Log</title><link>https://clews.id.au/til/jj-recover-file-from-operation-log/</link><pubDate>Tue, 19 May 2026 11:15:00 +1000</pubDate><guid>https://clews.id.au/til/jj-recover-file-from-operation-log/</guid><description>&lt;p&gt;Today I learned how to recover a single file from &lt;code&gt;jj&lt;/code&gt;&amp;rsquo;s operation log without
rewinding the whole repo. I thought I&amp;rsquo;d lost a 76-line markdown file of review
notes: it wasn&amp;rsquo;t in my working copy, and no commit in &lt;code&gt;jj log&lt;/code&gt; had it. A &lt;code&gt;jj op restore&lt;/code&gt; of the operation before the loss would have brought it back, but it
would also have rolled the &lt;em&gt;entire&lt;/em&gt; repo to that point, unwinding everything
I&amp;rsquo;d done since.&lt;/p&gt;</description></item><item><title>Jujutsu's rebase Has Three Different "Move" Verbs</title><link>https://clews.id.au/til/jj-rebase-r-s-b/</link><pubDate>Mon, 11 May 2026 11:00:00 +1000</pubDate><guid>https://clews.id.au/til/jj-rebase-r-s-b/</guid><description>&lt;p&gt;Today I got bitten by &lt;code&gt;jj rebase&lt;/code&gt;. I ran &lt;code&gt;jj rebase -r uqxnyrku -o main&lt;/code&gt;
expecting the descendants to come along, the way &lt;code&gt;git rebase&lt;/code&gt; moves a chain.
They didn&amp;rsquo;t. The descendant got reparented onto &lt;code&gt;uqxnyrku&lt;/code&gt;&amp;rsquo;s &lt;em&gt;old&lt;/em&gt; parent,
disconnecting it from the commit I&amp;rsquo;d just moved.&lt;/p&gt;
&lt;p&gt;The Git muscle-memory model, &amp;ldquo;rebase moves the chain&amp;rdquo;, doesn&amp;rsquo;t translate
directly. &lt;code&gt;jj&lt;/code&gt; makes you say &lt;em&gt;which&lt;/em&gt; chain:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Moves&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-r REV&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Just that single commit. Descendants get reparented to its old parent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-s REV&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;That commit &lt;em&gt;and all descendants&lt;/em&gt;. The whole subtree comes along.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-b REV&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The entire branch containing REV (everything between trunk and REV).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;-r&lt;/code&gt; snipping a commit out and leaving its descendants behind is occasionally
what you want (extracting one commit from a stack to relocate it) but it&amp;rsquo;s
almost never what Git muscle memory expects. &lt;strong&gt;&lt;code&gt;-s&lt;/code&gt; is the verb that matches
Git&amp;rsquo;s default rebase&lt;/strong&gt;: &amp;ldquo;move this and everything on top of it.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Jujutsu Has No git reset --hard: What to Use Instead</title><link>https://clews.id.au/til/jj-no-git-reset-hard/</link><pubDate>Sun, 03 May 2026 10:00:00 +1000</pubDate><guid>https://clews.id.au/til/jj-no-git-reset-hard/</guid><description>&lt;p&gt;Today I learned that there&amp;rsquo;s no single &lt;code&gt;jj&lt;/code&gt; equivalent of &lt;code&gt;git reset --hard&lt;/code&gt;,
and that&amp;rsquo;s by design. Muscle memory wants &lt;code&gt;git reset --hard&lt;/code&gt;, &lt;code&gt;git checkout .&lt;/code&gt;,
or &lt;code&gt;git stash drop&lt;/code&gt;, and &lt;code&gt;jj&lt;/code&gt; has none of them by name. It has several
different verbs, depending on which kind of &amp;ldquo;reset&amp;rdquo; you actually mean.&lt;/p&gt;
&lt;p&gt;The mental shift: in Git, &amp;ldquo;discard changes&amp;rdquo; usually means moving files between
zones (working tree → HEAD). In &lt;code&gt;jj&lt;/code&gt;, the working copy &lt;em&gt;is&lt;/em&gt; a commit, so
&amp;ldquo;discard changes&amp;rdquo; means either editing that commit&amp;rsquo;s contents or removing the
commit entirely. Pick the verb that matches the intent.&lt;/p&gt;</description></item><item><title>Jujutsu Can Refuse to Push Private WIP Commits</title><link>https://clews.id.au/til/jj-can-refuse-to-push-private-wip-commits/</link><pubDate>Sat, 25 Apr 2026 09:00:00 +1000</pubDate><guid>https://clews.id.au/til/jj-can-refuse-to-push-private-wip-commits/</guid><description>&lt;p&gt;Today I learned that &lt;a href="https://jj-vcs.github.io/jj/"&gt;Jujutsu&lt;/a&gt; has a built-in
guard that blocks specified commits from ever being pushed. You declare it with
a revset in your config:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# ~/.config/jj/config.toml (user-level → applies to every repo)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;private-commits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;description(glob:&amp;#34;wip:*&amp;#34;)&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now any commit whose description starts with &lt;code&gt;wip:&lt;/code&gt;, along with all its
descendants, is rejected by &lt;code&gt;jj git push&lt;/code&gt;, with a local check that fires before
any network contact:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Error: Won&amp;#39;t push commit 548e973f323b since it is private
Hint: Configured git.private-commits: &amp;#39;description(glob:&amp;#34;wip:*&amp;#34;)&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="parking-messy-local-only-work"&gt;Parking Messy Local-Only Work&lt;/h2&gt;
&lt;p&gt;The workflow for stashing scratch work where it can&amp;rsquo;t accidentally escape:&lt;/p&gt;</description></item><item><title>Jujutsu: less friction, same Git</title><link>https://clews.id.au/posts/jj-less-friction-same-git/</link><pubDate>Fri, 24 Apr 2026 12:51:27 +1000</pubDate><guid>https://clews.id.au/posts/jj-less-friction-same-git/</guid><description>&lt;p&gt;Jujutsu has been frequently turning up in my feeds over the past year. Always
the same pattern: someone extolling its virtues, a workflow that&amp;rsquo;d be tedious in
Git, a flurry of unfamiliar syntax. I&amp;rsquo;d skim, disengage, return to Git. My
workflow is simple. It works. A rebase conflict ruins the odd morning, the odd
stash gets forgotten. But&amp;hellip; that&amp;rsquo;s just Git, right? Why should an old git like
me switch?&lt;/p&gt;</description></item><item><title>Modernising My Zsh Setup</title><link>https://clews.id.au/posts/modernising-my-zsh-setup/</link><pubDate>Thu, 23 Apr 2026 14:30:00 +1000</pubDate><guid>https://clews.id.au/posts/modernising-my-zsh-setup/</guid><description>&lt;p&gt;A few weekends ago I noticed my terminal felt slow. Every new shell made me
wait before I could start typing. A quick measurement showed why:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; i in &lt;span class="o"&gt;{&lt;/span&gt;1..10&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; /usr/bin/time -p zsh -i -c &lt;span class="nb"&gt;exit&lt;/span&gt; 2&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep real&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;~720 ms of steady-state startup, paid on every tab, split, and &lt;code&gt;zellij&lt;/code&gt; pane. A
couple of days of tinkering later it was ~190 ms. This post covers how I got
there, and what I&amp;rsquo;d suggest to anyone still running a default oh-my-zsh +
powerlevel10k setup.&lt;/p&gt;</description></item><item><title>Reclaiming Disk Space from iCloud Drive on macOS</title><link>https://clews.id.au/til/reclaiming-disk-space-from-icloud-drive-on-macos/</link><pubDate>Fri, 13 Mar 2026 09:20:31 +1100</pubDate><guid>https://clews.id.au/til/reclaiming-disk-space-from-icloud-drive-on-macos/</guid><description>&lt;p&gt;Today I learned about &lt;code&gt;brctl evict&lt;/code&gt;, a macOS command that forcefully removes
local copies of iCloud Drive files while keeping them safely in the cloud. It
saved me ~400 GB.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;My MacBook was almost full: 934 GB of 995 GB used. I&amp;rsquo;d already told
iCloud Drive to optimise storage, but it wasn&amp;rsquo;t making a dent. The Storage panel
in System Settings showed &lt;strong&gt;695 GB&lt;/strong&gt; of &amp;ldquo;System Data&amp;rdquo;, which seemed absurd.&lt;/p&gt;</description></item><item><title>Advent of Code 2025, in Rust</title><link>https://clews.id.au/posts/advent-of-code-2025/</link><pubDate>Fri, 19 Dec 2025 09:28:22 +1100</pubDate><guid>https://clews.id.au/posts/advent-of-code-2025/</guid><description>&lt;p&gt;Last year I finally took the plunge and completed
&lt;a href="https://clews.id.au/posts/advent-of-code-2024/"&gt;Advent of Code 2024&lt;/a&gt; in Go. It was hard enough
that finishing meant something, and I wanted another go. So when December rolled
around again there was no question, except which language. This time: Rust.&lt;/p&gt;
&lt;p&gt;This year&amp;rsquo;s event was shorter, 12 days instead of the traditional 25, but the
puzzles were just as brain-bending as ever.&lt;/p&gt;
&lt;h2 id="why-rust"&gt;Why Rust?&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been building a CHIP-8 emulator in Rust this year, and it&amp;rsquo;s been the
project that finally made the language click for me. AoC felt like a good way to
keep that momentum going as it is smaller, self-contained puzzles where I could
practice without the overhead of a larger codebase.&lt;/p&gt;</description></item><item><title>Integer Linear Programming</title><link>https://clews.id.au/til/integer-linear-programming/</link><pubDate>Fri, 12 Dec 2025 14:07:40 +1100</pubDate><guid>https://clews.id.au/til/integer-linear-programming/</guid><description>&lt;p&gt;Today&amp;rsquo;s Advent of Code highlighted that a small change in problem constraints
can turn a straightforward solution into a freakin&amp;rsquo; hard optimisation problem.&lt;/p&gt;
&lt;h2 id="the-problem-factory-machine-configuration"&gt;The Problem: Factory Machine Configuration&lt;/h2&gt;
&lt;p&gt;You&amp;rsquo;re faced with factory machines that need configuration. Each machine has
buttons that affect multiple counters, and you need to figure out the minimum
number of button presses required.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Part 1&lt;/strong&gt;: Configure indicator lights (on/off states) by toggling them with
buttons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Part 2&lt;/strong&gt;: Configure joltage counters (integer values) by incrementing them
with buttons.&lt;/p&gt;</description></item><item><title>Kruskal's Algorithm: Building Minimum Spanning Trees with Union-Find</title><link>https://clews.id.au/til/kruskal/</link><pubDate>Tue, 09 Dec 2025 10:42:11 +1100</pubDate><guid>https://clews.id.au/til/kruskal/</guid><description>&lt;p&gt;Today I learned about Kruskal&amp;rsquo;s algorithm while solving
&lt;a href="https://adventofcode.com/2025/day/8"&gt;Advent of Code 2025 Day 8&lt;/a&gt;. Sort the edges,
add the short ones, skip anything that closes a cycle. That&amp;rsquo;s the whole thing,
and it&amp;rsquo;s optimal. Pairing it with Union-Find for the cycle check is what makes it
fast enough to run.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Given junction boxes suspended in 3D space, connect them with the minimum total
wire length such that electricity can flow between all boxes. For example, given
these 5 boxes:&lt;/p&gt;</description></item><item><title>Monotonic Stacks</title><link>https://clews.id.au/til/monotonic-stacks/</link><pubDate>Thu, 04 Dec 2025 10:10:20 +1100</pubDate><guid>https://clews.id.au/til/monotonic-stacks/</guid><description>&lt;p&gt;This is more of a &amp;ldquo;Today I Remembered&amp;rdquo;&amp;hellip;&lt;/p&gt;
&lt;p&gt;While solving &lt;a href="https://adventofcode.com/2025/day/3"&gt;Advent of Code Day 3&lt;/a&gt;, I was
reacquainted with an elegant pattern: &lt;strong&gt;monotonic stacks&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Given 15 digits, select exactly 12 (maintaining order) to form the largest
possible 12-digit number.&lt;/p&gt;
&lt;p&gt;Example: &lt;code&gt;&amp;quot;234234234234278&amp;quot;&lt;/code&gt; → &lt;code&gt;&amp;quot;434234234278&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="whats-a-monotonic-stack"&gt;What&amp;rsquo;s a Monotonic Stack?&lt;/h2&gt;
&lt;p&gt;A monotonic stack maintains elements in sorted order. When adding a new element:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pop elements that violate the ordering&lt;/li&gt;
&lt;li&gt;Push the new element&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why is this useful?&lt;/strong&gt; It lets you make greedy decisions while keeping the
ability to &amp;ldquo;revise&amp;rdquo; earlier choices. In this context a &lt;em&gt;greedy decision&lt;/em&gt; means
making the locally optimal choice at each step without looking ahead to future
possibilities. It can be thought of as &lt;em&gt;&amp;ldquo;When I see a larger digit, I should use
it instead of smaller digits I&amp;rsquo;ve already selected—as long as I can still reach
k total digits.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Rust's rem_euclid() - The Modulo You Actually Want</title><link>https://clews.id.au/til/rusts-rem-euclid-the-modulo-you-actually-want/</link><pubDate>Tue, 02 Dec 2025 10:00:00 +1100</pubDate><guid>https://clews.id.au/til/rusts-rem-euclid-the-modulo-you-actually-want/</guid><description>&lt;p&gt;Having spent a bit of time working with Python recently, I hit a surprising
gotcha with Rust&amp;rsquo;s &lt;code&gt;%&lt;/code&gt; operator today.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;I was implementing a circular dial (positions 0–99) that wraps around. In
Python, this just works:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="c1"&gt;# = 95 ✅&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But in Rust, the same logic fails:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// = -5 ❌
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Python&amp;rsquo;s &lt;code&gt;%&lt;/code&gt; operator follows mathematical modulo when the divisor is positive.
That means the result is always in the range [0, divisor)—ideal for wrap-around
arithmetic.&lt;/p&gt;</description></item><item><title>What Rust Actually Taught Me</title><link>https://clews.id.au/posts/chip-8-lessons-learned/</link><pubDate>Thu, 27 Nov 2025 10:00:00 +1100</pubDate><guid>https://clews.id.au/posts/chip-8-lessons-learned/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 2 of 2&lt;/strong&gt; | &lt;a href="https://clews.id.au/posts/chip-8-emulator-rust/"&gt;Part 1: Why CHIP-8&lt;/a&gt; | Part 2&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In &lt;a href="https://clews.id.au/posts/chip-8-emulator-rust/"&gt;Part 1&lt;/a&gt;, I explained why I picked CHIP-8 to learn
Rust and how I used AI as a mentor. Now: what actually stuck?&lt;/p&gt;
&lt;h2 id="the-compiler-as-teacher"&gt;The Compiler as Teacher&lt;/h2&gt;
&lt;h3 id="the-borrow-checker"&gt;The Borrow Checker&lt;/h3&gt;
&lt;p&gt;I expected to wrestle with the borrow checker constantly. That happened early.
Then I figured it out.&lt;/p&gt;
&lt;p&gt;When the compiler rejected my code, it was usually because I was doing something
subtly wrong, conceptually rather than syntactically. Holding a reference to
memory while modifying display state. Trying to share mutable data between threads
without synchronization.&lt;/p&gt;</description></item><item><title>Creating Type Stubs for Type Checking Without Installing Dependencies</title><link>https://clews.id.au/til/creating-type-stubs-for-type-checking-without-installing-dependencies/</link><pubDate>Mon, 24 Nov 2025 10:00:00 +1100</pubDate><guid>https://clews.id.au/til/creating-type-stubs-for-type-checking-without-installing-dependencies/</guid><description>&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;When type-checking Python code, tools like Pyright need to import all
dependencies to understand function signatures. But some packages have heavy
dependencies (NetCDF libraries, scientific computing stacks, databases) that
you don&amp;rsquo;t want to install just for type checking.&lt;/p&gt;
&lt;h2 id="the-solution-manual-type-stubs"&gt;The Solution: Manual Type Stubs&lt;/h2&gt;
&lt;p&gt;You can create a fake package in your virtual environment that provides type
information without any implementation.&lt;/p&gt;
&lt;h3 id="how-it-works"&gt;How It Works&lt;/h3&gt;
&lt;p&gt;Python&amp;rsquo;s import system doesn&amp;rsquo;t distinguish between packages installed via pip
and packages manually created in &lt;code&gt;site-packages&lt;/code&gt;. Both are treated identically.&lt;/p&gt;</description></item><item><title>I Built a CHIP-8 Emulator to Learn Rust</title><link>https://clews.id.au/posts/chip-8-emulator-rust/</link><pubDate>Thu, 20 Nov 2025 10:00:00 +1100</pubDate><guid>https://clews.id.au/posts/chip-8-emulator-rust/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 1 of 2&lt;/strong&gt; | Part 1 |
&lt;a href="https://clews.id.au/posts/chip-8-lessons-learned/"&gt;Part 2: What Rust Taught Me&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Every few months my organisation runs &lt;em&gt;Engineering Development Days&lt;/em&gt;. A few days
to learn something new without the distraction of work. Also,
&lt;a href="https://csirostaff.org.au/news/media/2025/08/08/recent-media-on-csiro-job-cuts/"&gt;job cuts&lt;/a&gt;,
fun times.&lt;/p&gt;
&lt;p&gt;I struggled to think of what to focus on. My role going forward is Python-heavy,
cloud-native. Any knowledge gaps I&amp;rsquo;d fill through actual work. I was bored,
feeling uninspired. I needed something compelling.&lt;/p&gt;</description></item><item><title>Quick Python Syntax Checks from the Command Line</title><link>https://clews.id.au/til/quick-python-syntax-checks-from-the-command-line/</link><pubDate>Fri, 07 Nov 2025 10:00:00 +1100</pubDate><guid>https://clews.id.au/til/quick-python-syntax-checks-from-the-command-line/</guid><description>&lt;p&gt;Today I learned a handy trick to check Python syntax without running your code.
Perfect for catching errors early.&lt;/p&gt;
&lt;h2 id="from-the-command-line"&gt;From the Command Line&lt;/h2&gt;
&lt;p&gt;You can run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m py_compile src/app/analysis.py &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;👍 Syntax is valid – no errors found&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or for another file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m py_compile src/app/server.wsgi &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;👍 server.wsgi syntax is valid&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="how-it-works"&gt;How it Works&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;python3 -m py_compile &amp;lt;file&amp;gt;&lt;/code&gt; attempts to compile the file to bytecode (.pyc)&lt;/li&gt;
&lt;li&gt;If compilation succeeds, the command after &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; runs (our little &amp;ldquo;syntax is
good&amp;rdquo; message)&lt;/li&gt;
&lt;li&gt;If there&amp;rsquo;s a syntax error, Python prints the error and the success message
won&amp;rsquo;t appear&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="programmatic-usage"&gt;Programmatic Usage&lt;/h2&gt;
&lt;p&gt;You can also do this programmatically in Python:&lt;/p&gt;</description></item><item><title>Cancelled Projects and the Art of Strategic Indifference</title><link>https://clews.id.au/posts/canceled-projects-and-the-art-of-strategic-indifference/</link><pubDate>Wed, 24 Sep 2025 15:57:32 +1100</pubDate><guid>https://clews.id.au/posts/canceled-projects-and-the-art-of-strategic-indifference/</guid><description>&lt;p&gt;There&amp;rsquo;s a moment every software engineer will eventually face: the project
you&amp;rsquo;ve poured months into gets shanked. Quietly. In a backroom meeting. Dead.
Done. Dusted. Over. Finito.&lt;/p&gt;
&lt;p&gt;Not because it was bad. Not because it didn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;Because&amp;hellip; someone changed their mind. An executive shuffled a roadmap. A reorg
happened. Someone&amp;rsquo;s ego got bruised. The &lt;em&gt;&amp;ldquo;vision&amp;rdquo;&lt;/em&gt; moved on, as it always does.&lt;/p&gt;
&lt;p&gt;And just like that, it&amp;rsquo;s dead.&lt;/p&gt;
&lt;p&gt;No retrospective. No launch party. No legacy.&lt;/p&gt;</description></item><item><title>Running a Bert Model on an iPhone</title><link>https://clews.id.au/posts/running-a-bert-model-on-an-iphone-a-three-day-journey-from-data-center-to-pocket/</link><pubDate>Mon, 03 Mar 2025 14:44:48 +1100</pubDate><guid>https://clews.id.au/posts/running-a-bert-model-on-an-iphone-a-three-day-journey-from-data-center-to-pocket/</guid><description>&lt;p&gt;During our recent Engineering Development Days, a three-day event where we
paused regular work to focus on personal growth, I set myself one goal: take a
large pre-trained BERT model built for GPU-heavy environments and get it running
on an iPhone. I&amp;rsquo;d never worked with BERT models, Core ML or mobile deployment
before, so most of the three days was spent finding out what I didn&amp;rsquo;t know.&lt;/p&gt;
&lt;h2 id="the-challenge-bringing-a-data-center-model-to-mobile"&gt;The Challenge: Bringing a Data Center Model to Mobile&lt;/h2&gt;
&lt;p&gt;Our team has recently built a text classification API that categorises inputs
into labels such as &lt;code&gt;condition&lt;/code&gt;, &lt;code&gt;constraint&lt;/code&gt;, &lt;code&gt;notice&lt;/code&gt;, and &lt;code&gt;process&lt;/code&gt;. The
engine behind this API was a BERT-large model. BERT-large has 24 transformer
layers, a hidden size of 1024, 16 attention heads, and an intermediate layer
size of 4096. Those numbers are why it&amp;rsquo;s good at the task, and also why it&amp;rsquo;s
expensive to run.&lt;/p&gt;</description></item><item><title>Setting Up Postgresql 16 and Postgis on Macos With Homebrew</title><link>https://clews.id.au/til/setting-up-postgresql-16-and-postgis-on-macos-with-homebrew/</link><pubDate>Mon, 03 Feb 2025 15:42:42 +1100</pubDate><guid>https://clews.id.au/til/setting-up-postgresql-16-and-postgis-on-macos-with-homebrew/</guid><description>&lt;p&gt;Recently, I needed to install PostgreSQL 16 with PostGIS on my MacBook M1 Pro
(running macOS 15.2), and what I assumed would be a quick &lt;code&gt;brew install postgis&lt;/code&gt;
turned into a tedious debugging session.&lt;/p&gt;
&lt;p&gt;As of now, the Homebrew postgis formula is linked to PostgreSQL 14. To use
PostGIS with PostgreSQL 16, you&amp;rsquo;ll need to compile PostGIS manually. Here are
the notes from my experience.&lt;/p&gt;
&lt;h2 id="tldr-if-youre-in-a-hurry-heres-the-script"&gt;TL;DR: If You&amp;rsquo;re in a Hurry, Here&amp;rsquo;s the Script&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Stop PostgreSQL Services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew services stop postgresql
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Remove existing PostGIS and PostgreSQL@14 to avoid conflicts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew remove postgis
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew remove postgresql@14
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Reinstall json-c (must be done after removing old postgis)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew uninstall json-c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install json-c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew link json-c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Install PostgreSQL 16 (if not already installed)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install postgresql@16
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo ln -sf /opt/homebrew/Cellar/postgresql@16/16.4/bin/postgres /usr/local/bin/postgres
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo chown -R &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;:admin /usr/local/bin /usr/local/share
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Install necessary utilities&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install wget
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install pcre
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Fix issues with missing gettext headers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew reinstall gettext
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew unlink gettext &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; brew link gettext --force
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Install PostGIS dependencies&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install geos gdal libxml2 sfcgal protobuf-c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Download and build PostGIS 3.5.2 from source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget https://download.osgeo.org/postgis/source/postgis-3.5.2.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -xvzf postgis-3.5.2.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm postgis-3.5.2.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; postgis-3.5.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Use the correct gettext version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GETTEXT_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew info gettext &lt;span class="p"&gt;|&lt;/span&gt; grep -Eo &lt;span class="s1"&gt;&amp;#39;stable [0-9]+\.[0-9]+&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $2}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./configure &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --with-projdir&lt;span class="o"&gt;=&lt;/span&gt;/opt/homebrew/opt/proj &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --with-pgconfig&lt;span class="o"&gt;=&lt;/span&gt;/opt/homebrew/opt/postgresql@16/bin/pg_config &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --with-jsondir&lt;span class="o"&gt;=&lt;/span&gt;/opt/homebrew/opt/json-c &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --with-sfcgal&lt;span class="o"&gt;=&lt;/span&gt;/opt/homebrew/opt/sfcgal/bin/sfcgal-config &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --with-pcredir&lt;span class="o"&gt;=&lt;/span&gt;/opt/homebrew/opt/pcre &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --without-protobuf &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --without-topology &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LDFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$LDFLAGS&lt;/span&gt;&lt;span class="s2"&gt; -L/opt/homebrew/Cellar/gettext/&lt;/span&gt;&lt;span class="nv"&gt;$GETTEXT_VERSION&lt;/span&gt;&lt;span class="s2"&gt;/lib&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-I/opt/homebrew/Cellar/gettext/&lt;/span&gt;&lt;span class="nv"&gt;$GETTEXT_VERSION&lt;/span&gt;&lt;span class="s2"&gt;/include&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;make
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;make install
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-pain-points"&gt;The Pain Points&lt;/h2&gt;
&lt;h3 id="1-homebrew"&gt;1. &lt;strong&gt;Homebrew&amp;rsquo;s PostgreSQL Versions Can Clash&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If you have &lt;code&gt;postgresql@14&lt;/code&gt; or an older version installed, you might run into
conflicts. You might see errors like:&lt;/p&gt;</description></item><item><title>Advent of Code 2024</title><link>https://clews.id.au/posts/advent-of-code-2024/</link><pubDate>Tue, 28 Jan 2025 08:43:40 +1100</pubDate><guid>https://clews.id.au/posts/advent-of-code-2024/</guid><description>&lt;p&gt;Every December, &lt;a href="https://adventofcode.com"&gt;Advent of Code&lt;/a&gt; turns up in my feeds
and I feel roughly equal parts keen and dread. It’s a yearly programming
challenge that wraps algorithmic problems in a festive narrative.&lt;/p&gt;
&lt;p&gt;For years, I flirted with the idea of participating. And for years, I always
found a way to dodge it. Sometimes, I’d forget about it until a stray tweet
reminded me a week into the event, leaving me feeling hopelessly behind. Other
times, summer holidays and the thought of spending time glued to a screen felt
like a crime. And, honestly? The puzzles kind of intimidated me. They felt like
they were tailor-made to expose every weak spot I had. It was always easier to
skip it than risk confirming my doubts.&lt;/p&gt;</description></item><item><title>Simplifying Remote Docker Builds With SSH and Contexts</title><link>https://clews.id.au/til/simplifying-remote-docker-builds-with-ssh-and-contexts/</link><pubDate>Tue, 03 Dec 2024 10:03:12 +1100</pubDate><guid>https://clews.id.au/til/simplifying-remote-docker-builds-with-ssh-and-contexts/</guid><description>&lt;p&gt;Recently, I’ve been making a conscious effort to eliminate lazy practices from
my workflow: the kind of &amp;rsquo;temporary&amp;rsquo; workarounds that sneak in during crunch
time and somehow become permanent habits. My latest offender? A convoluted
method for building containers on remote machines. Instead of using a branch
like any reasonable person (for reasons I can’t even justify), I’d generate a
git patch locally, SCP it to the remote machine, apply it there, and then build.
It’s the software equivalent of taking the scenic route while ignoring the
perfectly good highway right in front of me.&lt;/p&gt;</description></item><item><title>Taming the Noise: Decoding Kubernetes Logs for Humans</title><link>https://clews.id.au/posts/taming-the-noise-decoding-kubernetes-logs-for-humans/</link><pubDate>Fri, 29 Nov 2024 15:38:16 +1100</pubDate><guid>https://clews.id.au/posts/taming-the-noise-decoding-kubernetes-logs-for-humans/</guid><description>&lt;p&gt;&lt;a href="https://github.com/bclews/hallucino"&gt;Hallucino&lt;/a&gt;, my Kubernetes log analyser,
came out of a rare two days with no deadlines attached. During an &lt;em&gt;Engineering
Development Day&lt;/em&gt; event, a pause to focus on personal growth, I had a go at
building an &lt;em&gt;&amp;ldquo;intelligent&amp;rdquo;&lt;/em&gt; tool that could make sense of the noisy, chaotic
world of Kubernetes logs.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Kubernetes logs are a labyrinth. In large, distributed systems, logs pour in
from countless containers, pods, and nodes, and the lines that would actually
help you resolve an outage are buried under everything else. I wanted to see
whether a Large Language Model plus Go&amp;rsquo;s concurrency could turn that stream into
something a little more human readable.&lt;/p&gt;</description></item><item><title>Managing Dotfiles With Gnu Stow</title><link>https://clews.id.au/til/managing-dotfiles-with-gnu-stow/</link><pubDate>Wed, 06 Nov 2024 13:53:32 +1100</pubDate><guid>https://clews.id.au/til/managing-dotfiles-with-gnu-stow/</guid><description>&lt;p&gt;Dotfiles can be a pain to manage when they’re scattered across your home
directory. In the past, I used a bare Git repo approach based on
&lt;a href="https://news.ycombinator.com/item?id=11070797"&gt;StreakyCobra’s setup on Hacker News&lt;/a&gt;.
It worked well but had its limitations, like needing a custom alias for Git
commands (which I’d often forget) and some tricky troubleshooting when configs
broke.&lt;/p&gt;
&lt;p&gt;Recently, though, I switched to using
&lt;a href="https://www.gnu.org/software/stow/"&gt;GNU Stow&lt;/a&gt; after reading
&lt;a href="https://brandon.invergo.net/news/2012-05-26-using-gnu-stow-to-manage-your-dotfiles.html"&gt;Brandon Invergo’s guide&lt;/a&gt;.
Stow is a simple symlink manager originally for managing software installed from
source. It keeps all your dotfiles in one directory and links them to where they
need to be, so your home directory stays tidy and the whole lot is easy to
version.&lt;/p&gt;</description></item><item><title>About</title><link>https://clews.id.au/about/</link><pubDate>Sat, 18 May 2024 22:12:34 +1000</pubDate><guid>https://clews.id.au/about/</guid><description>&lt;p&gt;&lt;strong&gt;Hey! I&amp;rsquo;m Ben Clews, a software engineer calling Hobart, Tasmania home.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I spend my days turning complex tech challenges into elegant solutions, currently as a Software Engineer at CSIRO&amp;rsquo;s Data61. We&amp;rsquo;re building some cool stuff that helps researchers manage their data and run powerful analytics - basically making it easier for science to make a real impact.&lt;/p&gt;
&lt;p&gt;When I&amp;rsquo;m not coding, you&amp;rsquo;ll find me hunting out new music, whisky and craft chocolate (yes, probably in that order). I brew small batches of beer, attempt to bake bread (with varying success), and love exploring new cities with my camera in hand.&lt;/p&gt;</description></item></channel></rss>