the first of the weekly updates
Due to having some extra time this summer, I've decided to start writing weekly update posts, and this one is the first.
They will cover what I've done across public (open-source) projects as well as private ones, so some parts won't make much sense if you're not me. I'll explain any terms that are overloaded or unlikely to mean much to you.
Each section corresponds to a project or repository, in no particular order. Paragraphs within each section are sorted in rough chronological order.
#web
My websites, web services, and all related infrastructure (as code).
The big one for this week was rewriting the bundling engine. Before, it was a naïve MVP, bundling JavaScript and CSS by concatenating the files. This worked fine when I had only a file or two, but it didn't scale. The first version's workaround was Unix run-parts-style numeric prefixes to ensure a specific concatenation order, but that was always meant to be temporary.
The replacement uses ES modules and CSS imports, as it should: main.js or main.css as entry points, and each one can import siblings using the standard import or @import directives, respectively. Bundling is handled by plugging directly into esbuild's Go API and serving files from the embedded in-memory filesystem.
I removed a host system dependency from the build process. Previously, the build recipe would run go generate before triggering the container build, which (among other things) invoked starfield. Now, database code is generated within the build container.
I devised a simple way to deploy without visible downtime, and without running multiple containers: add a small delay to Caddy that rides out the short gap while a deploy swaps the containers. The connection is held open until the new container is ready (which is quick anyway), then allowed to continue.
For better auditability, deployments now tag the commit being deployed, so there's a record of what's currently in production. If the working tree is dirty, the changes are committed as a wip commit first, so the tag still captures exactly what shipped.1
Other minor changes, not worth much detail: trimmed the Docker build context somewhat by ensuring .dockerignore was up to date, added a landing page for an upcoming project, and researched what it would take to completely get rid of internal port numbers, using Unix domain sockets instead.
I tagged version 5 to finally complete the milestone of the Go rewrite. The specific details are worthy of their own post, which may come later.
#system
Everything related to my local personal systems and their configuration (as code).
The bulk of this week's work went towards my pi subagents extension. This is an extension that orchestrates child agents using pi --mode rpc to complete tasks in parallel. Up until now there was only one type of subagent, a general-purpose exploration agent with access to read-only tools only (read, find, grep, ls). Now there are two:
The Analyst is the new name for the OG subagent. It has read-only tools, a medium thinking level, and it's generally good at doing many things.
The Oracle is a term I borrowed from Amp. In this context it refers to an agent that's set to the most intelligent model and the highest thinking level possible. It's there for the genuinely hard problems, where slower and deeper reasoning is worth the extra cost.
Subagent configuration is centralised, so I'll easily be able to add new types, if the need arises. I've contemplated adding a worker with write tools, for the job of implementing plans, but I haven't felt it was necessary just yet.
The final subagent-related change was a reminder added to the status output, telling the agent it's free to end its turn. Without it, agents would sometimes get stuck in a loop, calling the status tool repeatedly.
I changed the Bash command guard config file to be more explicit. The guard evaluates whether an agent can run a command by parsing it and checking it against a set of rules, outputting a JSON object with a decision. The decision (action) was optional, defaulting to deny. It's now mandatory.
I updated several skills, none worth too much detail: agents were told not to offer to commit; task tracking became a mandatory part of the workflow; /handoff restricted its output to file paths with line ranges; screenshots were rerouted to a different directory; some skills were added, split up, and deleted.
Three notable skill additions: webapp (how to drive headless Chromium over CDP for interactive testing and debugging), mdbook (best practices for writing technical documentation), guard-review (how to ensure that guard's configuration doesn't allow unsafe commands).
I consolidated Goscript completion handling from three tiers into one. Eventually completions will be handled by an upcoming reimplementation of docopt, but for now it just uses a thrown-together version of what may become its AST. I also created the first version of the plan for that reimplementation.
The box container manager learned to create needed directories before Docker could step in and create them as root, leaving them inaccessible.
#cmd
Released v1.13.0, after trying and failing to switch to a 2-part versioning scheme. It was going well until I discovered that Go modules expect 3-part semantic versions. Now we're using a 3-part scheme and just ignoring the final part.
Added chronic, a Go port of moreutils' chronic, with long options. I needed this as a static binary for a system too niche to run moreutils' version.
Added cmdctl, which generates a script containing completion functions for all the installed tools, concatenated. This can be loaded into your shell sessions by passing it through eval in your .bashrc.
#diffr
A subset of a full-featured visual git diff tool, written in Rust using Tauri.
The main diffr window had a glaring issue: the current repository wasn't displayed in its title. I expected this to be simple, but it wasn't. I had to switch to creating the main Tauri window programmatically via WebviewWindowBuilder instead of the standard way of declaring it in tauri.conf.json.
The webview data directory was being created at a path that didn't follow the existing convention for where my projects store their configuration, so I had to override how Tauri resolves that directory. It turns out the fix for the title would've been needed here anyway: passing data_directory to the WebviewWindowBuilder is how it's done.
#housekeeping
Across various projects:
- Updated the pinned URL in
mise.lockafter mise changed it upstream - Trimmed the hadolint config down to only the rules actually needed
- Suppressed false-positive gosec complaints
-
Ad-hoc deploys should be used responsibly. ↩︎