<?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>Wed, 27 May 2026 11:30:00 +1000</lastBuildDate><atom:link href="https://clews.id.au/index.xml" rel="self" type="application/rss+xml"/><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, having used 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: A Rust-Flavored Journey</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 challenging,
rewarding, and left me wanting more. So when December rolled around again, there
was no question: I was coming back for round two. This time, though, I wanted to
push myself in a different direction: 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;</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 &lt;strong&gt;a small change in problem constraints
can transform a straightforward solution into a freakin&amp;rsquo; hard optimisation
problem.&lt;/strong&gt;&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;. What struck me
most was how elegantly the greedy approach works. It seems almost too simple to
be optimal, yet the proof is surprisingly straightforward. Pairing it with
Union-Find for cycle detection makes the whole algorithm both intuitive and
efficient.&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-why-chip8/"&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-why-chip8/"&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-is-a-teacher"&gt;The Borrow Checker Is a Teacher&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 tools, databases, etc.)
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 &lt;strong&gt;fake package&lt;/strong&gt; 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 tackled a challenge that was
both technically demanding and rewarding. The goal was straightforward yet
ambitious: adapt a large pre-trained BERT model, originally designed for
GPU-heavy environments, to run efficiently on an iPhone. This was uncharted
territory for me, as I had never previously worked with BERT models, Core ML or
mobile deployment. The learning curve was steep, yet fun.&lt;/p&gt;</description></item><item><title>Setting Up Postgresql 16 and Postgis on Macos With Homebrew</title><link>https://clews.id.au/posts/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/posts/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, an event pops up in the software development world that stirs
excitement, dread, and inspiration in equal measure:
&lt;a href="https://adventofcode.com"&gt;Advent of Code&lt;/a&gt;. It’s a yearly programming challenge
that combines festive themes with algorithmic problem-solving, drawing in
developers from all walks of life.&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/posts/simplifying-remote-docker-builds-with-ssh-and-contexts/</link><pubDate>Tue, 03 Dec 2024 10:03:12 +1100</pubDate><guid>https://clews.id.au/posts/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;Some projects emerge from necessity, others from curiosity.
&lt;a href="https://github.com/bclews/hallucino"&gt;Hallucino&lt;/a&gt;, my Kubernetes log analyser,
came from a rare chance to set aside deadlines, deliverables, and sprint boards
in favour of learning and exploration. During an &lt;em&gt;Engineering Development Day&lt;/em&gt;
event, a two-day pause to focus on personal growth, I dove into the challenge
of crafting 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 goldmine of information, but also a labyrinth. In large,
distributed systems, logs pour in from countless containers, pods, and nodes.
Amid this flood are hidden gems: critical insights that can resolve outages,
optimise performance, or detect brewing problems. But the sheer volume often
buries their value. I wanted to explore the possibilities of combining modern
Large Language Models (LLMs) with Go’s concurrency capabilities to transform
unstructured logs 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/posts/managing-dotfiles-with-gnu-stow/</link><pubDate>Wed, 06 Nov 2024 13:53:32 +1100</pubDate><guid>https://clews.id.au/posts/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. The beauty of Stow is that it keeps all dotfiles neatly organized in one
directory and links them to where they need to be, which keeps your home
directory clean and makes version control easier.&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>