<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>EE Heng Chen — Blog</title><description>EE Heng Chen&apos;s build logs: Claude Code plugins, MCP tooling and LLM compliance projects, each told as a series that follows the project.</description><link>https://cheneeheng.github.io/</link><item><title>Agentic AI journal 1/N - the beginnings with 7 lessons</title><link>https://cheneeheng.github.io/blog/agentic-ai-journal--the-beginnings/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/agentic-ai-journal--the-beginnings/</guid><description>Six months with Claude Code, from usage paranoia and a code tsunami to a short addiction, skills, Opus, and seven lessons along the way.</description><pubDate>Tue, 20 Oct 2026 00:00:00 GMT</pubDate></item><item><title>Nine birthplace codes the registration department never published</title><link>https://cheneeheng.github.io/blog/presidio-compliance-stack--nine-codes-jpn-never-published/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/presidio-compliance-stack--nine-codes-jpn-never-published/</guid><description>The NRIC recognizer accepted a range of place-of-birth codes on a recall argument. Reading all 294 rows of the official table showed nine of them don&apos;t exist.</description><pubDate>Tue, 20 Oct 2026 00:00:00 GMT</pubDate></item><item><title>The hash chain verifies, and the record is still wrong</title><link>https://cheneeheng.github.io/blog/presidio-compliance-stack--a-chain-that-verifies-a-wrong-record/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/presidio-compliance-stack--a-chain-that-verifies-a-wrong-record/</guid><description>Measuring my own audit store turned up a concurrency claim that didn&apos;t hold, and a case where a record names a recognizer that found nothing while the chain still verifies.</description><pubDate>Thu, 15 Oct 2026 00:00:00 GMT</pubDate></item><item><title>879 of 1000 records, and no torn line to notice</title><link>https://cheneeheng.github.io/blog/presidio-compliance-stack--879-of-1000/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/presidio-compliance-stack--879-of-1000/</guid><description>Building the v1 plans for a Malaysian PII pack and its audit store surfaced four places the plans were wrong — the worst was an audit log that silently lost records on Windows.</description><pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate></item><item><title>Without the audit layer it was a recognizer pack with Malaysian regexes</title><link>https://cheneeheng.github.io/blog/presidio-compliance-stack--the-audit-layer-had-to-earn-it/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/presidio-compliance-stack--the-audit-layer-had-to-earn-it/</guid><description>Before building a Malaysian PII pack for Presidio, I made the audit layer argue for its place in both directions. It survived, on a narrower reason than the one I started with.</description><pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate></item><item><title>The wheel is built, audited, and parked</title><link>https://cheneeheng.github.io/blog/pr-compliance-gate--built-audited-not-published/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/pr-compliance-gate--built-audited-not-published/</guid><description>Packaging the gate for PyPI found four real bugs and produced a release workflow that has never run — committed as publish.yml.disabled, because a package on PyPI is a claim I can&apos;t make yet.</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>Three unrelated bugs, one symptom, and a fully covered test suite</title><link>https://cheneeheng.github.io/blog/pr-compliance-gate--three-ways-to-disable-a-sub-agent/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/pr-compliance-gate--three-ways-to-disable-a-sub-agent/</guid><description>The investigator was broken by an event loop, a missing environment variable, and a structured-output method that quietly stopped guaranteeing anything — none of which a 100%-covered suite could see.</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>The sandbox root came out of the request body</title><link>https://cheneeheng.github.io/blog/pr-compliance-gate--the-sandbox-root/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/pr-compliance-gate--the-sandbox-root/</guid><description>Every path the investigator resolved was checked against its sandbox. The sandbox itself was located by an unvalidated id from the POST body — so the guard was fine and the ground under it moved.</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>Three read-only tools and a budget the model doesn&apos;t get to spend</title><link>https://cheneeheng.github.io/blog/pr-compliance-gate--three-tools-and-a-budget/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/pr-compliance-gate--three-tools-and-a-budget/</guid><description>The investigator is the only part of the gate that goes off and reads a repository by itself, so most of its design is a list of things it cannot do — including fail.</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>Every investigation ended at a human, and a reducer was why</title><link>https://cheneeheng.github.io/blog/pr-compliance-gate--routing-is-code/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/pr-compliance-gate--routing-is-code/</guid><description>Routing in the gate is a pure-Python rule ladder, not an LLM call — but the list reducer underneath it meant an investigation could never actually resolve anything.</description><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate></item><item><title>A pull-request reviewer I built to find out whether I could</title><link>https://cheneeheng.github.io/blog/pr-compliance-gate--proving-it-to-myself/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/pr-compliance-gate--proving-it-to-myself/</guid><description>A portfolio piece, plainly — a LangGraph and Agent SDK pipeline that reviews PRs against policy, built around two cost gates that keep most traffic away from the expensive models.</description><pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate></item><item><title>The parameter that looked completely innocent</title><link>https://cheneeheng.github.io/blog/mcp-cassette--the-innocent-parameter/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/mcp-cassette--the-innocent-parameter/</guid><description>An echo tool grew a callback_url and lint had nothing to say about it — which is why the cassette gate has a second step that only asks whether the server&apos;s surface moved.</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate></item><item><title>I built a test fixture and it turned out to be a supply-chain artifact</title><link>https://cheneeheng.github.io/blog/mcp-cassette--a-supply-chain-surface/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/mcp-cassette--a-supply-chain-surface/</guid><description>A cassette is a verbatim transcript of what a third-party server said, committed to your repository and fed to a model — so lint reads it for injection smells, and its rule packs are TOML on purpose.</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>Streamable HTTP by hand, and a sampling request that never arrived</title><link>https://cheneeheng.github.io/blog/mcp-cassette--the-sampling-request-that-never-arrived/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/mcp-cassette--the-sampling-request-that-never-arrived/</guid><description>Adding remote transport meant framing SSE myself to keep the dependency list short — and then a recording fixture hung because the server was talking on a stream nobody was listening to.</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate></item><item><title>Four red jobs on my first CI run, and Windows was the one that passed</title><link>https://cheneeheng.github.io/blog/mcp-cassette--four-red-jobs/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/mcp-cassette--four-red-jobs/</guid><description>The clean POSIX shutdown path had never run anywhere. CI ran it on four machines at once and found two bugs the blunt Windows path had already solved.</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>A buffered read, a greedy argparse, and one line I&apos;m not proud of</title><link>https://cheneeheng.github.io/blog/mcp-cassette--the-transport-fought-back/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/mcp-cassette--the-transport-fought-back/</guid><description>Three things about stdio nearly stopped the recording proxy from working at all — and shutting it down cleanly on Windows ended in os._exit(130) after I gave up on the correct design.</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate></item><item><title>Testing an MCP server meant paying the agent every time</title><link>https://cheneeheng.github.io/blog/mcp-cassette--paying-the-agent/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/mcp-cassette--paying-the-agent/</guid><description>I wanted to start a project with MCP and found there was no easy way to test the interaction without calling the agent over and over — so I built the vcrpy equivalent for it.</description><pubDate>Thu, 27 Aug 2026 00:00:00 GMT</pubDate></item><item><title>The smallest thing in here, and I forgot it was running</title><link>https://cheneeheng.github.io/blog/claude-code-command-center--session-prefixer/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-command-center--session-prefixer/</guid><description>The smallest tool in my Claude Code monorepo — a wrapper that auto-names every unnamed session with its folder and a timestamp, so the session history is actually browsable.</description><pubDate>Tue, 25 Aug 2026 00:00:00 GMT</pubDate></item><item><title>I let a copy win, and it ate my status line</title><link>https://cheneeheng.github.io/blog/claude-code-command-center--file-sync/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-command-center--file-sync/</guid><description>file-sync keeps my Claude config in step across two folders, newer wins either way — until a raw copy clobbered a machine-specific statusLine.command and taught me why settings.json needs a merge, not a copy.</description><pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate></item><item><title>Harvesting lessons from sessions I&apos;d never reopen</title><link>https://cheneeheng.github.io/blog/claude-code-command-center--session-digests/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-command-center--session-digests/</guid><description>Claude sessions are disposable, so I harvest their lessons into a weekly master file on a schedule — plus a hand-run skill I built for a separate credit pot Anthropic announced but never shipped.</description><pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate></item><item><title>The same file, quietly different in every repo</title><link>https://cheneeheng.github.io/blog/claude-code-command-center--cross-repo-diff/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-command-center--cross-repo-diff/</guid><description>I&apos;d copied the same CLAUDE.md and configs across a dozen repos and let them drift — Vantage is a single HTML file that surveys every repo, diffs any two files, and copies the good version across.</description><pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate></item><item><title>Planning got so easy I drowned in plans</title><link>https://cheneeheng.github.io/blog/docket--drowning-in-plans/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/docket--drowning-in-plans/</guid><description>Planning got cheap enough to do from my phone, so plans piled up across ten repos — docket is the single board I run them all from, one at a time or batched by project.</description><pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate></item><item><title>I couldn&apos;t tell which skill would fire</title><link>https://cheneeheng.github.io/blog/claude-code-command-center--component-browser/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-command-center--component-browser/</guid><description>Two skills, same name, no way to tell which one Claude Code fires — so I built a browser that lists every skill, agent, and hook on the machine, shows which one wins, and lets me read what each actually does.</description><pubDate>Tue, 04 Aug 2026 00:00:00 GMT</pubDate></item><item><title>CLI is all the rage, so I made one</title><link>https://cheneeheng.github.io/blog/claude-code-command-center--usage-report/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-command-center--usage-report/</guid><description>I already had a usage dashboard; I built the terminal version out of pure &quot;can I&quot; curiosity — and it&apos;s what finally justified extracting the shared parsing into a library.</description><pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate></item><item><title>Where all my tokens actually went</title><link>https://cheneeheng.github.io/blog/usage-dashboard--where-tokens-go/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/usage-dashboard--where-tokens-go/</guid><description>The Claude Code status line shows the moment; I wanted the story — so I built a dashboard that breaks token usage down by project, tool, and model, with a rate-limit trajectory that tells me when I&apos;ll hit the wall.</description><pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate></item><item><title>I couldn&apos;t see the wall until I hit it</title><link>https://cheneeheng.github.io/blog/claude-code-command-center--statusline/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-command-center--statusline/</guid><description>My Claude Code status line started as a context gauge back in the 200k days, grew rate limits and a cost figure — and why I trust two of those three and squint at the third.</description><pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate></item><item><title>The monorepo I wouldn&apos;t have built before Claude Code</title><link>https://cheneeheng.github.io/blog/claude-code-command-center--consolidation/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-command-center--consolidation/</guid><description>Why I pulled a dozen standalone Claude Code tools into one monorepo called C4 — and why that would have been a maintenance trap before Claude Code could read the whole thing.</description><pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate></item><item><title>figureflow worked. I shut it down anyway.</title><link>https://cheneeheng.github.io/blog/figureflow--sunset/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/figureflow--sunset/</guid><description>I built figureflow to answer two questions — can you wrap React Flow in Python, and can an LLM author interactive diagrams with it. Both got answered. The second answer is why I archived it.</description><pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate></item><item><title>Put any CLI in the browser: xterm.js, node-pty, and Docker</title><link>https://cheneeheng.github.io/blog/claude-code-html-wrapper--cli-in-the-browser/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-html-wrapper--cli-in-the-browser/</guid><description>The generic recipe underneath my Claude Code wrapper — a WebSocket-to-PTY bridge into a container, written down with the five things that actually cost me time.</description><pubDate>Tue, 23 Jun 2026 00:00:00 GMT</pubDate></item><item><title>I put Claude Code in the browser</title><link>https://cheneeheng.github.io/blog/claude-code-html-wrapper--in-the-browser/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-html-wrapper--in-the-browser/</guid><description>A self-hosted web front end for Claude Code — one Docker container per user, sessions you can resume from a browser tab, and no cloud in the loop.</description><pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate></item><item><title>Three layers I refused to build</title><link>https://cheneeheng.github.io/blog/claude-code-html-wrapper--lessons/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-html-wrapper--lessons/</guid><description>My Claude Code wrapper has no database, no Docker SDK, and no permission prompts — each absence was a decision made in the spec, and each one is why the server fits in seven files.</description><pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate></item><item><title>I asked Claude what made its design look generated</title><link>https://cheneeheng.github.io/blog/claude-code-plugin-toggler--de-generification/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-plugin-toggler--de-generification/</guid><description>The redesign looked AI-generated and I couldn&apos;t say why — so I asked the model that designed it to critique it. It listed its own tells, and iteration 19 became a subtraction pass.</description><pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate></item><item><title>Teal, because of the orange</title><link>https://cheneeheng.github.io/blog/claude-code-plugin-toggler--tidewater/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-plugin-toggler--tidewater/</guid><description>Reskinning my Claude Code plugin manager around teal and terracotta — a palette that stays related to Claude without looking like an Anthropic product, and a VSCode panel that does the same trick against the editor.</description><pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate></item><item><title>The plugin scope I skipped was the repo&apos;s own stance</title><link>https://cheneeheng.github.io/blog/claude-code-plugin-toggler--scopes/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-plugin-toggler--scopes/</guid><description>Going from a two-tier local/global plugin model to three scopes — Local, Project, User — because the missing one lets a repo carry its own opinions to whoever works in it.</description><pubDate>Sun, 07 Jun 2026 00:00:00 GMT</pubDate></item><item><title>The plugin manager I didn&apos;t write</title><link>https://cheneeheng.github.io/blog/claude-code-plugin-toggler--claude-builds-it/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-plugin-toggler--claude-builds-it/</guid><description>Every line of my Claude Code plugin manager was written by Claude Code. What I actually did all those days — specs, sequencing, and being the first user — and where the model out-debugged me.</description><pubDate>Thu, 04 Jun 2026 00:00:00 GMT</pubDate></item><item><title>The Windows bug that came back three times</title><link>https://cheneeheng.github.io/blog/claude-code-plugin-toggler--lessons/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-plugin-toggler--lessons/</guid><description>I promised to write up the path problems once I understood them. It took three releases, a 29-line band-aid, and Claude Code finding the one character I couldn&apos;t see.</description><pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate></item><item><title>Five plugins, five round trips</title><link>https://cheneeheng.github.io/blog/claude-code-plugin-toggler--plugin-manager/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/claude-code-plugin-toggler--plugin-manager/</guid><description>Setting up yet another project with my usual Claude Code plugins, I got annoyed enough to build a toggler — a browser UI and a VSCode panel over one JSON file.</description><pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate></item><item><title>New blog, new direction</title><link>https://cheneeheng.github.io/blog/hello/</link><guid isPermaLink="true">https://cheneeheng.github.io/blog/hello/</guid><description>Resetting the blog. Expect short, occasional notes on building AI coding agents and the tooling around them.</description><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate></item></channel></rss>