punctilio (n.): precise observance of formalities.
Pretty good at making your text pretty. The most feature-complete and reliable English typography package.punctilio transforms plain ascii into typographically correct Unicode, even across html element boundaries. Try it live atturntrout.com/punctilio.
Smart quotes · Em / en dashes · Ellipses · Math symbols · Legal symbols · Arrows · Primes · Fractions · Superscripts · Ligatures · Non-breaking spaces · html-aware · Markdown support · Bri’ish, German, and French localisation support
import { transform } from "punctilio";transform(`"It's a beautiful thing, the destruction of words..." -- 1984`);// → “It’s a beautiful thing, the destruction of words…”—1984
punctilio accepts three input formats: text, Markdown, and html. Use it as a library, a CLI, a pre-commit hook, or—for a zero-friction install in any project that already runs Prettier—a Prettier plugin.
I open source my website infrastructure and article edit histories atalexander-turner/TurnTrout.com. I license the repository under CC by-sa 4.0, which means you can share and adapt the site as long as you provide attribution and distribute any derivative works under the same license.
When I started writing in 2018, I didn’t include alt text. Over the years, over 500 un-alt’ed images piled up. These (mostly) aren’t simple images of geese or sunsets. Most of my images are technical, from graphs of experimental results to hand-drawn AI alignment comics. Describing these assets was a major slog, so I turned to automation.
To implement accessibility best practices, I needed alt text that didn’t describe the image so much as communicate the information the image is supposed to communicate. None of the scattershot AI projects I found met the bar, so I wrote my own package.
alt-text-llm is an AI-powered tool for generating and managing alt text in Markdown files. Originally developed for this website,alt-text-llm streamlines the process of making web content accessible. The package detects assets missing alt text, suggests context-aware descriptions, and provides an interactive reviewing interface in the terminal.
Generating alt text for maze diagrams from Understanding and Controlling a Maze-Solving Policy Network.alt-text-llm displays the surrounding text (above the image), the image itself in the terminal usingimgcat, and the llm-generated alt suggestion. The user interactively edits or approves the text.
In the end, I generated over 550 high-quality alt-text suggestions for about $12.50 using Gemini 2.5 Pro. Withalt-text-llm, I addressed hundreds and hundreds of alt-less images: detecting them; describing them; reviewing them; and lastly applying my finalized alts to the original Markdown files.turntrout.com is now friendlier to the millions of people who browse the web with the help of screen readers.
Dataset contamination is bad for several reasons. Most obviously, when benchmarks are included in AI training data, those benchmarks no longer measure generalization—the AI may have been directly taught the answers. Even more concerningly, if your data promote negative “stereotypes” about AIs, they might become self-fulfilling prophecies, training future models to exhibit those same behaviors.
In the Claude 4 system card, Anthropic revealed that approximately 250,000 transcripts from their alignment faking paper had been scraped from the public web and included in their pretraining data. This caused an early model to hallucinate details from the paper’s fictional scenarios, forcing Anthropic to implement unique mitigations. Speculatively, this kind of misalignment data could degrade the alignment of any models trained thereafter.1
Data scraping practices are a serious problem. The tool we are currently releasing will not stop state-of-the-art actors. Since I wanted to at least mitigate the problem, I put out a bounty for a simple, open source tool to harden data against scraping. The tool is now ready:easy-dataset-share. In less than 30 minutes and at a cost of $0, you can deploy a download portal with basic protections against scrapers, serving a canary-tagged dataset with modest protections against AI training.
easy-dataset-share will not stop sophisticated scrapers
Sophisticated scraping operations can bypass Cloudflare Turnstile for about $0.001 cents per trial (via e.g. CapSolver). Therobots.txt and Terms of Service are not technically binding and rely on the good faith of the user, although the ToS does provide limited legal deterrence. Canary strings can be stripped from documents. Overall, this tool is just a first step towards mitigating dataset contamination. We later discuss improvements which might protect against sophisticated actors.
One command to set up your shell, editor, and secret management.
My.dotfiles repository provides comprehensive development environment setup. With this command, I quickly personalize any shell—even if I’m just visiting withssh for a few hours.
Fish shell with autocomplete, syntax highlighting, and thetide theme,
neovim via LazyVim, providing a full ide experience,
tmux with automatic session saving and restoration,
envchain for hardware-encrypted secret management via macOS Secure Enclave or Linux gnome-keyring—no more plaintext api keys in configuration files,
Open source AI tool setup,
autojump for quick directory navigation,
Reversible file deletion by default viatrash-put instead ofrm,
git aliases and other productivity shortcuts, and—drum roll—
goosesay, because every terminal needs more geese.
______________________________________ / Find out just what any people will \ | quietly submit to and you have the | | exact measure of the injustice and | | wrong which will be imposed on them. | \ --- Frederick Douglass / -------------------------------------- \ \ \ ___ .´ ""-⹁ _.-´) e _ '⹁ '-===.<_.-´ '⹁ \ \ \ ; \ ; \ _ | '⹁__..--"" ""-._ _.´) / ""-´ _> : -´/ ; .__< __) \ '._ .__.-' .-´ '⹁_ '-⹁__.-´ / '-⹁__/ ⹁ _.´ ____< /'⹁__/_."" .´.----´ | | .´ / | | ´´-/ ___| ; <_ / `.'´
Each time I open thefish shell, a rainbow goose blurts out an interesting phrase. I spent several hours to achieve this modern luxury.
if status is-interactive fortune 5% computers 5% linuxcookie 2% startrek 88% wisdom | cowsay -f ~/.dotfiles/apps/goose.cow | lolcat -S 6end
The way this works is that:
I sample a saying by calling thefortune command,
I pipe the saying intogoosesay (my variant of the cow in the originalcowsay),
Thelolcat command splays the text ’cross the rainbow.
The originalsubfont traced font usage from scratch on every page. That took almost two hours per deploy. My fork groups pages by their css and only traces one representative per group, extracting just the text from the rest (for my site: 382 ⭢ 5 css traces). Those remaining traces run in parallel across worker threads.
A GitHub template that makes Claude Code work reliably on your repositories. It wires up git hooks, CI workflows, and Claude session hooks so that Claude can autonomously fix code, create PRs, and respond to@claude mentions—with safeguards to prevent broken code from shipping.
Without this template, using Claude Code on a repo requires manually configuring hooks, writing CI workflows, and building guardrails against common failure modes (infinite retry loops, pushing broken code, inconsistent formatting).
With this template, you get all of that out of the box:
A solid starting claude.md—upholds high code quality standards, including a self-critique loop that catches bugs before they leave the editor
Pre-push verification—build, lint, type checks, and tests run automatically before everygit push orgh pr create
Deadlock-proof session hooks—every hook is syntax-checked at session start, wrapped in a launcher that degrades to “ask” on parse failure, and commits with conflict markers are rejected up front
Skill-driven PR flow—thepr-creation skill runs an iterative compress-critique-fix loop on the diff, then watches CI and fixes failures before reporting back
Enforced code quality—Conventional Commits (via commitlint), Prettier formatting, andlint-staged run on every commit
@claude GitHub integration—mention Claude in issues or PR comments and it responds with full repo context
Weekly security sweeps—a scheduled workflow collects Dependabot, code-scanning, secret-scanning, andpnpm audit alerts, then hands them to Claude to open a rollup fix PR
Automatic template sync—downstream repos receive improvements daily via PR, with 3-way merge that preserves your customizations
Multi-language support—Node.js (pnpm), Python (uv / ruff / pytest), and shell (shfmt / shellcheck) work out of the box
When handling dangerous materials, a chemist doesn’t yolo it barehanded on the open bench. They reach through a glovebox to do their work.
Right now, you’re probably barehanding AI, tossing it into a shell onto your machine with access to your credentials. At most, you’re shielded by a single prompted “auto-mode” classifier. Doing better is hard and while there are a few papers on best practices, it’s not clear how to do it properly and you have experiments to run.
After all, what’s the chance that something bad happens?
glovebox is a sealed enclosure that only gives the agent what it needs to do your work. Tap-tap-tapglovebox and press Enter to spin up a hardware-isolated, allowlist-firewalled microVM, with a (currently experimental) AI monitor with a red-alert ability to push-notify your phone and halt the AI until you return. The goal is a minimal-friction secure experience that gets the job done.
glovebox is an Inspect sandbox provider in addition to a coding-agent wrapper. The goal:pip install inspect-glovebox, thensandbox="glovebox" on a Task gives every eval sample its own microVM.
A reusable GitHub Actions workflow that clears a pull request’s merge conflicts for you. It merges the base branch into the pull request, then pushes the result as an ordinary merge commit.
The workflow resolves a conflict in three passes. Only the last one spends model tokens.
The pre-pass rebuilds every conflicted generated file instead of guessing it. A lockfile goes through its own lock command, and a generated artifact through its generator.
The structural pre-pass re-merges what is left, syntax-aware. It runs mergiraf, which this workflow installs at a pinned version from its own tree. mergiraf merges a conflict by the file’s syntax rather than its lines, so it settles many source conflicts for free. Nothing reaches the model that this pass already solved.
A model resolves the source conflicts that remain. With the pre-pass configured, the model sees only files a person wrote by hand. Without it the model sees every remaining conflict, generated files included. That is what a fork head gets, because the workflow emptiesresolver-mjs there, and what a repository that declares no rules gets. A generator that fails sends its file to the model the same way. A pre-pass that could not run at all refuses instead, because it re-derived nothing: a missing binary or module names itself in the command’s own output.
A conflict that no pass can settle stops the run and comments on the pull request. A binary file is one example, and a-merge file that no rule owns is another. This check runs before any model call, so an unresolvable conflict costs nothing.
You call the workflow from your own repository byuses:, pinned to a commit with the release version in a trailing comment—see Versioning.
A green check does not always mean the work passed. These pre-commit lints find the places where CI reports success over a real failure. They are fast, and they run offline.
Here is the smallest case. A CI step runspytest | tee log. A shell pipe reports the exit code of its last command, andtee always succeeds. The tests fail, the step exits 0, and the check turns green. Nobody re-reads a green check, so the failure stays.
Two families of defects behave this way, and neither one shows up as a red check:
The pipeline hides a failure. A pipe drops the exit code of the command that failed. Or a required check never reports, so the pull request waits forever.
You cannot prove what you ran. A base image or a download names a tag or a bare url. That target can change, so the bytes you run may differ from the bytes you reviewed.
Then add the Tier 1 aggregate to your.pre-commit-config.yaml:
repos: - repo: https://github.com/AlexanderMattTurner/ci-truth-serum rev: v1.2.0 # the release tag; matches the package version (vX.Y.Z) hooks: - id: check-tier1 # every honesty + identity + security check
Then runpre-commit run --all-files once, to sweep the files you already have.
The pack sorts its checks on two axes, and the Usage section shows how to select on each:
A tier says how much of your CI architecture a check assumes. Tier 1 fits any repository. Tier 2 assumes one specific architecture, so enable it only if you follow that architecture. Extras are bonus checks about tests, docs, shell, and Python rather than CI reporting.
A tag says what a check is about:honesty, security, cost, and eleven more.
Each table row below names the hook id, then the failure it prevents. Most rows follow one pattern: what goes wrong, why CI stays green anyway, a real measured incident, the fix, and the comment token that opts one line out. The last column is a one-line example of the defect.
Cleans untrusted text before your agent reads it. An attacker hides a payload where a person cannot see it but the model still reads it: invisible Unicode, ansi escapes, human-hidden html, confusable glyphs, look-alike hosts, and exfil-shaped urls. This library handles each channel. Easy to use as a Claude Code plugin, and the plugin itself is recommended byalignment-hive!
import { sanitize } from "agent-sanitizer";// Layer 1 (invisible chars + ANSI), zero heavy deps:const { cleaned, found, warnings, notes } = await sanitize(untrustedText);// Opt into the HTML layers for web ingress (lazy-loads ~200 ms of deps):const result = await sanitize(pageSource, { html: true });// Layer 3 alone: flag exfil-shaped URLs without splicing anything (for text// that must stay byte-faithful, e.g. a PR diff). Implied by `html: true`.const scanned = await sanitize(diffText, { exfilScan: true });// Layer 3 reads an exact-digest-length hex value under a generic parameter// name (`?v=<md5>`, an ETag, a commit id) as a fingerprint. `flagDigestValues`// reports it as payload instead — more false positives, no 16-to-64-byte// channel under a name the caller picks. For monitors, not for splicing.const strict = await sanitize(logText, { exfilScan: true, flagDigestValues: true,});
sanitize never throws and never silently drops content—any change comes with
at least onewarnings ornotes entry.found names the neutralized category
codes (e.g.["cf-format", "hidden-html"]); cleaned is the safe text, with
placeholders where hidden html was spliced out. See warnings vs
notes for which findings land where.
My site uses Mermaid diagrams for compact, searchable, and dynamically styled graphics. However, thea11y accessibility checker revealed a “bigly” problem. When a page contains multiple diagrams, some element IDs collide (like those for arrowhead markers and node containers). Thus, callingurl(#arrowhead) in the third diagram might bind to a marker defined in the first. Arrowheads vanish, click handlers fire on the wrong diagram, and css styles corrupt.
The issue had been reported repeatedly since 2020 (#1,318,#3,267,#3,433,#4,346) with no comprehensive fix in sight. In February 2026, I aimed Claude Code at this problem. My PR 7,410 prefixed every element ID with its diagram’s unique svg container ID, making collisions impossible. I also ensured that all future diagram types generate unique IDs across diagrams.
KaTeX’s TypeScript codebase carried dozens of escape-hatchas any casts left over from its earlier Flow-to-TypeScript migration. Each one silenced a type error instead of fixing it, so the compiler couldn’t catch bugs in those spots. PR 4,171 replaced these with proper checks and tighter type definitions, then enables a lint rule so newany casts can’t sneak back in.
About MeI’m TurnTrout, but the United States government insists on calling me “Alexander Matt Turner.” I like writing and learning about lots of stuff.