<?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: Flude team</title>
    <description>The latest articles on DEV Community by Flude team (@flude_team).</description>
    <link>https://dev.to/flude_team</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%2F4043834%2F57eca9f0-8b66-4e9d-b493-6c13ecd57dfd.png</url>
      <title>DEV Community: Flude team</title>
      <link>https://dev.to/flude_team</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/flude_team"/>
    <language>en</language>
    <item>
      <title>The Phantom That Wasn't There: How Unreliable grep Made Us Chase Ghosts</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Fri, 18 Sep 2026 04:58:58 +0000</pubDate>
      <link>https://dev.to/flude_team/the-phantom-that-wasnt-there-how-unreliable-grep-made-us-chase-ghosts-29fn</link>
      <guid>https://dev.to/flude_team/the-phantom-that-wasnt-there-how-unreliable-grep-made-us-chase-ghosts-29fn</guid>
      <description>&lt;p&gt;One of the hardest stages in developing a documentation generator is alignment with legacy tools. When you migrate from an old, decades-proven proprietary generator to your own custom pipeline, you are obligated to prove that you haven't lost a single class.&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%2Fg8jw5divy9yzyx8ao50h.jpg" 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%2Fg8jw5divy9yzyx8ao50h.jpg" alt="The Phantom Bug Grep" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We wrote an automated system that compared thousands of entities generated by the old tool with what our new Flude-based pipeline found. And one day, we stumbled upon a terrifying anomaly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anomaly in the Matrix
&lt;/h2&gt;

&lt;p&gt;The alignment script happily reported: Flude found and generated documentation for a struct named &lt;code&gt;GraphicsAsset&lt;/code&gt; and its smart pointer &lt;code&gt;GraphicsAssetPtr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem was that in the old, reference documentation, these entities didn't exist. At all.&lt;/p&gt;

&lt;p&gt;According to our logic, if the legacy generator didn't find the class, it's highly likely this class simply doesn't exist, and our parser made a mistake somewhere, "gluing" someone else's names together. We had to check the source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero Results
&lt;/h2&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%2Fr6r1onq44qs8nc7r0gqd.jpg" 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%2Fr6r1onq44qs8nc7r0gqd.jpg" alt="Zero grep results" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We did what any engineer would do—we opened a terminal and ran a quick global text search (&lt;code&gt;grep&lt;/code&gt; / &lt;code&gt;ripgrep&lt;/code&gt;) across the entire multi-gigabyte codebase of our enterprise C++ product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rg &lt;span class="s2"&gt;"GraphicsAsset"&lt;/span&gt; ./sdk_sources/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: &lt;strong&gt;0 matches&lt;/strong&gt;. Absolute emptiness.&lt;/p&gt;

&lt;p&gt;We checked with case-insensitivity, we searched for the smart pointer. Nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Panic: The Hallucinating Parser
&lt;/h2&gt;

&lt;p&gt;It was a moment of genuine horror. The codebase didn't contain the string &lt;code&gt;GraphicsAsset&lt;/code&gt;. But somehow, our parser hadn't just found this name; it had built a beautiful HTML page for it, complete with a list of methods, properties, and inheritance!&lt;/p&gt;

&lt;p&gt;The conclusion seemed obvious: our parser had gone insane. It had started fabricating C++ classes out of thin air. Perhaps the logic for expanding complex macros had broken, or Doxygen's cache had become so corrupted that it started stitching pieces of different files into a single "Frankenstein".&lt;/p&gt;

&lt;p&gt;We were already preparing to open the debugger and rewrite the token-linking logic in the core.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autopsy of the Cache
&lt;/h2&gt;

&lt;p&gt;Fortunately, before tearing apart the core codebase, we decided to look at the raw intermediate data. We went to the directory where Doxygen dumped its low-level XML, and searched there.&lt;/p&gt;

&lt;p&gt;Suddenly, sitting quietly on the disk, was a file named &lt;code&gt;struct_graphics_asset.xml&lt;/code&gt;. We opened it.&lt;/p&gt;

&lt;p&gt;Inside was perfectly formed XML. It didn't look like a hallucination. Moreover, in the &lt;code&gt;&amp;lt;location&amp;gt;&lt;/code&gt; tag, the parser pedantically pointed out exactly where it got this class:&lt;br&gt;
&lt;code&gt;Core/Include/Graphics/MaterialConfig.h:3006-3061&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Phantom Takes Flesh
&lt;/h2&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%2Ftvx3l7dpk9cjqar1jgzm.jpg" 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%2Ftvx3l7dpk9cjqar1jgzm.jpg" alt="MaterialConfig.h macro" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We opened the &lt;code&gt;MaterialConfig.h&lt;/code&gt; file and scrolled down to line 3006.&lt;/p&gt;

&lt;p&gt;And there, in the very heart of a massive legacy header, hid a completely real, living struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;CORE_ABSTRACT&lt;/span&gt; &lt;span class="n"&gt;GRAPHICS_EXPORT&lt;/span&gt; &lt;span class="n"&gt;GraphicsAsset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... dozens of lines of valid C++ code&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It had always been there. The legacy generator simply couldn't parse it because of complex macros, but our Flude could.&lt;/p&gt;

&lt;p&gt;So why did the global &lt;code&gt;rg&lt;/code&gt; yield zero matches? In enterprise monoliths, there are always plenty of reasons. Perhaps the &lt;code&gt;Core/Include&lt;/code&gt; path was listed in a tricky &lt;code&gt;.gitignore&lt;/code&gt;, and the smart &lt;code&gt;ripgrep&lt;/code&gt; ignored it. Perhaps the file had an exotic Windows encoding that broke the search. Perhaps it was a mounted submodule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A global text search offered an absence of evidence as evidence of absence.&lt;/p&gt;

&lt;p&gt;This case taught us a crucial rule: &lt;strong&gt;a text search is not a C++ parser&lt;/strong&gt;. Trying to analyze the structure of a C++ monolith with regular expressions or &lt;code&gt;grep&lt;/code&gt; is a path to disaster and false conclusions.&lt;/p&gt;

&lt;p&gt;If you doubt your parser, never use text search as the arbiter. Trust only the raw dumps of the compiler itself or the Abstract Syntax Tree (AST). And most importantly: do not try to "fix" a bug in the core until you prove its existence in the actual source of truth.&lt;/p&gt;

&lt;p&gt;But parsing and cache problems paled in comparison to our infrastructural challenges. From day one, our project was built as an ecosystem of several independent repositories. How this architecture turned into Git traps and nearly broke our Continuous Integration—read about it in the next episode.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/the-phantom-bug-grep" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/the-phantom-bug-grep&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>c</category>
      <category>debugging</category>
      <category>grep</category>
      <category>parsing</category>
    </item>
    <item>
      <title>The Leak That Won't Die: How We Broke Python's tempfile</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Tue, 15 Sep 2026 03:52:53 +0000</pubDate>
      <link>https://dev.to/flude_team/the-leak-that-wont-die-how-we-broke-pythons-tempfile-95o</link>
      <guid>https://dev.to/flude_team/the-leak-that-wont-die-how-we-broke-pythons-tempfile-95o</guid>
      <description>&lt;p&gt;Production crashes always happen suddenly. This time, the alarm was raised by the core C++ SDK developers. Their scheduled nightly build failed on the CI runner with a classic diagnosis: &lt;code&gt;No space left on device&lt;/code&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%2Fzqqegw2r4vml7rdc0f3o.jpg" 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%2Fzqqegw2r4vml7rdc0f3o.jpg" alt="The Leak That Wont Die" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We ran out of disk space. A quick post-mortem revealed that the &lt;code&gt;/tmp&lt;/code&gt; folder was bursting with thousands of temporary directories. Their names all started the same way: &lt;code&gt;ude_xml_&lt;/code&gt;. It was our code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture of the Crash
&lt;/h2&gt;

&lt;p&gt;As we've mentioned before, our engine handles Doxygen in a tricky way. We feed it source code, it spits out a massive XML dump, and we parse that dump. For every run, the engine created a temporary folder using &lt;code&gt;tempfile.mkdtemp()&lt;/code&gt;. A dynamically generated &lt;code&gt;Doxyfile&lt;/code&gt; was placed there, and Doxygen would dump hundreds of megabytes of XML into it.&lt;/p&gt;

&lt;p&gt;The plan was simple: at the very end of the pipeline, a &lt;code&gt;cleanup()&lt;/code&gt; function would run, executing an honest &lt;code&gt;shutil.rmtree()&lt;/code&gt; on that folder. But the plan had a flaw. If the parsing failed with an exception, the pipeline aborted, &lt;code&gt;cleanup()&lt;/code&gt; was never called, and a folder filled with megabytes of garbage was left stranded in &lt;code&gt;/tmp&lt;/code&gt; forever.&lt;/p&gt;

&lt;p&gt;Complaints started pouring in from teams building massive projects. The error was flaky. We tried wrapping the call in a &lt;code&gt;try/finally&lt;/code&gt; block, but the engine's logic was smeared across dozens of classes. The leak persisted.&lt;/p&gt;

&lt;h2&gt;
  
  
  A False Sense of Security: Trusting the GC
&lt;/h2&gt;

&lt;p&gt;How do you solve the problem of forgotten resource cleanup in Python? "Delegate the cleanup to the Garbage Collector (GC)!" we thought.&lt;/p&gt;

&lt;p&gt;Instead of &lt;code&gt;mkdtemp&lt;/code&gt;, we rewrote the logic to use the &lt;code&gt;tempfile.TemporaryDirectory&lt;/code&gt; object. Its main advantage is that when the object is deleted from memory, its finalizer (&lt;code&gt;__del__&lt;/code&gt;) automatically triggers the folder's deletion on disk.&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%2F0e73026d4rcra6lho5ii.jpg" 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%2F0e73026d4rcra6lho5ii.jpg" alt="Python Garbage Collector Overload" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To ensure the folder lived exactly as long as the engine needed it, we saved a reference to the object in a global list inside the manager class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;temp_dir_obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ude_xml_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_active_temp_dirs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp_dir_obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It seemed like elegant, "pythonic" code. The object lives in the manager's memory and is automatically deleted when the script finishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fatal Mistake
&lt;/h2&gt;

&lt;p&gt;We rolled out this "fix" to production. For a few days, the complaints stopped. We patted ourselves on the back and closed the issue. And then the CI runners started crashing again.&lt;/p&gt;

&lt;p&gt;We missed one "tiny" detail. Hoping that objects are &lt;em&gt;always&lt;/em&gt; deleted, and that the Garbage Collector works &lt;em&gt;immediately&lt;/em&gt;, is a ticking time bomb.&lt;/p&gt;

&lt;p&gt;In the case of giant C++ projects like Flude, the engine parsed dozens of modules sequentially. A new directory was created for each module. Because of a complex web of circular references in our parsers (e.g., an AST node referenced its parent class, and the class held a list of its children), the engine's objects weren't deleted immediately. They hung in memory, waiting for a full garbage collection cycle. And, of course, &lt;code&gt;_active_temp_dirs&lt;/code&gt; stayed in memory too.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TemporaryDirectory&lt;/code&gt; objects piled up in RAM by the hundreds. But the worst part was this: when the GC finally got to them and called &lt;code&gt;cleanup()&lt;/code&gt;, it triggered a &lt;strong&gt;massive synchronous deletion of files from the disk&lt;/strong&gt;, blocking the main execution thread for several minutes. Because of this, other services on the server began failing due to timeouts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Fix
&lt;/h2&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%2Fy8w88ac6ynkbv3cev6hb.jpg" 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%2Fy8w88ac6ynkbv3cev6hb.jpg" alt="Context Manager containment" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The conclusion of our investigation was grim: using finalizers to free heavy system resources is an anti-pattern. We couldn't control &lt;em&gt;when&lt;/em&gt; the GC would trigger, which meant we couldn't control the disk I/O load.&lt;/p&gt;

&lt;p&gt;we had to rewrite the pipeline's architecture so that the lifecycle of temporary folders was managed by strict &lt;code&gt;with&lt;/code&gt; context managers, not the GC.&lt;/p&gt;

&lt;p&gt;We introduced a strict rule: creating the folder and calling &lt;code&gt;cleanup()&lt;/code&gt; must happen in the same place. If any part of the engine needs these files, it accesses them inside a &lt;code&gt;with&lt;/code&gt; block, guaranteeing that upon exiting the block (even via an exception), the folder is deleted immediately.&lt;/p&gt;

&lt;p&gt;To protect against regressions, we also added a "janitor"—a mechanism that forcibly checks &lt;code&gt;/tmp&lt;/code&gt; before a build starts and cleans up any orphaned garbage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Orphaned temporary directory found: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Caller failed to invoke cleanup(). Force-cleaning now.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We learned a harsh lesson. Shifting the responsibility for system resources to Python's garbage collector is a direct path to disaster.&lt;/p&gt;

&lt;p&gt;But our victory over the garbage was just the beginning. While we were wrestling with Python, the real enemy was lurking in the very heart of our infrastructure. We thought we had tamed Doxygen, but it soon took its revenge, forcing us to rewrite the entire parser from scratch. How a simple C++ construct drove the documentation generator insane—read about it in the next episode.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/the-leak-that-wont-die" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/the-leak-that-wont-die&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>devops</category>
      <category>memoryleak</category>
      <category>garbagecollection</category>
    </item>
    <item>
      <title>The Illusion of Speed: A Two-Level Cache Can't Speed Up What Doxygen Can't Skip</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Thu, 10 Sep 2026 17:17:18 +0000</pubDate>
      <link>https://dev.to/flude_team/the-illusion-of-speed-a-two-level-cache-cant-speed-up-what-doxygen-cant-skip-3560</link>
      <guid>https://dev.to/flude_team/the-illusion-of-speed-a-two-level-cache-cant-speed-up-what-doxygen-cant-skip-3560</guid>
      <description>&lt;p&gt;Every single full documentation rebuild ruthlessly executed &lt;code&gt;doxygen.exe&lt;/code&gt; and parsed its massive XML output. The tool simply didn't care if you fixed a single typo in the comments or rewrote half the SDK. On real projects this translated into minutes of agonizing waiting after every push, even though the vast majority of the work could have been safely skipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Fingerprints for Cache Control
&lt;/h2&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%2F95hk147xblykamc7jxin.jpg" 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%2F95hk147xblykamc7jxin.jpg" alt="Two cache levels: XML and IR" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We built the caching scheme around two independent fingerprints to strictly divide responsibilities. The &lt;code&gt;xml_fingerprint&lt;/code&gt; determines whether the Doxygen run can be skipped entirely. We compute it as a content hash spanning the binary, the effective &lt;code&gt;Doxyfile&lt;/code&gt;, and the source tree. Modification time checks were thrown out immediately to prevent fresh CI checkouts from falsely invalidating the cache. The &lt;code&gt;OUTPUT_DIRECTORY&lt;/code&gt; parameter also had to be excluded from the hash to avoid a circular dependency between the fingerprint and the generated files.&lt;/p&gt;

&lt;p&gt;The second fingerprint, &lt;code&gt;ir_fingerprint&lt;/code&gt;, decides the fate of parsing the generated XML into a &lt;code&gt;ProjectCatalog&lt;/code&gt; structure. It incorporates the first hash but adds parser configuration like &lt;code&gt;exclude_swig_internals&lt;/code&gt; and the logic hash. Any minor tweak to the parsing rules instantly invalidates the saved internal representation (IR). We managed to catch a potential state desync bug before writing any code thanks to a couple of independent AI review sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundaries of Compute Optimization
&lt;/h2&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%2Fgofs4jr4nuyuhxlmlg60.jpg" 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%2Fgofs4jr4nuyuhxlmlg60.jpg" alt="Cache invalidation risk" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The compute time cache abruptly stops at the IR boundary. The actual transformation of the &lt;code&gt;ProjectCatalog&lt;/code&gt; object into final Markdown is calculated from scratch every time. Observant readers might spot a &lt;code&gt;BuildCacheManager&lt;/code&gt; class in &lt;code&gt;storage.py&lt;/code&gt; that handles a rendering cache. It operates purely on file I/O to protect local Hugo livereload from triggering falsely.&lt;/p&gt;

&lt;p&gt;We dropped the idea of caching the render computation for very pragmatic reasons. Rendering a single page is heavily tied to global state, such as the &lt;code&gt;sidebar.toml&lt;/code&gt; navigation tree and cross-links. Altering the taxonomy structure breaks the layout of dozens of files simultaneously, making invalidation tracking incredibly complex. Furthermore, Doxygen processing takes long minutes, while traversing the ready IR tree takes mere seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Speed Remains an Illusion
&lt;/h2&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%2Fdqrf5shkuzwae2bxqxtz.jpg" 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%2Fdqrf5shkuzwae2bxqxtz.jpg" alt="Monolithic rebuild" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our two-level cache only knows how to do one thing—completely skip a build step when fingerprints match. Doxygen physically lacks an incremental parsing mode and cannot run any faster. Any altered character in the sources forces us to endure a full parser run. True incrementality, where only the modified file gets recomputed, is fundamentally incompatible with a monolithic XML dump.&lt;/p&gt;

&lt;p&gt;In the architectural plans for the next major version we have already documented dropping Doxygen in favor of direct AST construction via &lt;code&gt;libclang&lt;/code&gt; or &lt;code&gt;tree-sitter&lt;/code&gt;—the choice between the two hasn't been made yet; the requirements treat them as equal candidates of the same technical direction. Incrementality becomes a natural property of the tool there, instead of something bolted on top with fingerprints—but that same shift will force us to throw away and rewrite the existing caching logic entirely. For now, we had to face the harsh limits of the free GitHub Actions cloud, which pushed us to build our own self-hosted runner. In the next episode we cover how we escaped to our own hardware.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/illusion-of-speed-caching" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/illusion-of-speed-caching&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>performance</category>
      <category>caching</category>
      <category>doxygen</category>
      <category>flude</category>
    </item>
    <item>
      <title>Flude.guide: The Name We Had to Find Between Udemy, a Debugger, and nude.com</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Tue, 08 Sep 2026 03:38:44 +0000</pubDate>
      <link>https://dev.to/flude_team/fludeguide-the-name-we-had-to-find-between-udemy-a-debugger-and-nudecom-5cee</link>
      <guid>https://dev.to/flude_team/fludeguide-the-name-we-had-to-find-between-udemy-a-debugger-and-nudecom-5cee</guid>
      <description>&lt;p&gt;When the time finally arrived to launch this blog, an uncomfortable reality surfaced. The internal working title "UDE" was completely dead on arrival. We faced problems far beyond simple domain availability. Someone else already owned &lt;code&gt;ude.com&lt;/code&gt; and &lt;code&gt;ude.io&lt;/code&gt;, but the fatal blow came from trademark law. The abbreviation is already a registered ® trademark—Universal Debug Engine, a well-known microcontroller debugging tool from Germany's PLS. Launching another developer tool under the exact same name guarantees a swift cease-and-desist letter. We preferred shipping software over arguing with corporate lawyers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three filters running in parallel
&lt;/h2&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%2Fldphj41zbznj9n6dvsv1.jpg" 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%2Fldphj41zbznj9n6dvsv1.jpg" alt="Naming search: from UDE to Flude" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We started hunting for a four- or five-letter replacement built around the "ude" substring. The evaluation process was brutal—and immediate. Sound, SEO potential, and legal safety were checked simultaneously. Any candidate could trip an alarm on any axis. One strike instantly eliminated the word from contention.&lt;/p&gt;

&lt;p&gt;Candidates like "nude", "dude", and "jude" crashed out on the sound check right away. Several variants barely registered as real English words. The archaic Scots term "blude" would confuse anyone outside of Edinburgh. Common words like "nude" or "dude" carry bizarre baggage that a strict technical reference manual desperately wants to avoid.&lt;/p&gt;

&lt;p&gt;The word "Etude" survived slightly longer. It sounds pleasant and bypasses direct trademark violations. The fatal flaw hides in its marketability. A massive global cosmetics brand already dominates search results for that term. Trying to build a presence under that banner means fighting through endless irrelevant noise. The original "UDE" title suffered from a similar chronic condition. Even ignoring the legal threat, the abbreviation looks far too much like the Udemy course platform.&lt;/p&gt;

&lt;p&gt;We eventually landed on "Flude". It possessed no magical qualities or overwhelming strengths. It simply stood as the first candidate that survived all three filters without breaking apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claiming the right zone
&lt;/h2&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%2Fivuh38i7blt2xdytduvi.jpg" 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%2Fivuh38i7blt2xdytduvi.jpg" alt="Choosing the TLD" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the name locked in, we needed a home on the internet. Premium extensions usually sit trapped in squatter portfolios. Buying a catchy short domain for thousands of dollars makes zero sense for a young project. We refused to negotiate with domain brokers.&lt;/p&gt;

&lt;p&gt;A quick search showed availability in the &lt;code&gt;.dev&lt;/code&gt; space at standard registrar pricing. It sounds reasonable, except that suffix heavily implies writing code and libraries. We needed a space dedicated to final technical documentation. The &lt;code&gt;.guide&lt;/code&gt; extension sat wide open, cost barely anything, and literally described our product. The choice made itself. We secured a flawless match between our brand name and its actual function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maturing alongside the infrastructure
&lt;/h2&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%2F5aq8cupywakal9uj79z7.jpg" 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%2F5aq8cupywakal9uj79z7.jpg" alt="Renaming Cloudflare Projects" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We locked down the domain while the brand was still baking internally. The new identity eventually cemented itself deep inside our infrastructure. We purged the outdated "ude-" prefix from all Cloudflare Pages projects. A massive single-day rename swept through both the user documentation and the engine repository.&lt;/p&gt;

&lt;p&gt;Next time, we will dive into the illusion of speed. We will demonstrate why a clever two-layer cache of XML and syntax trees completely fails to accelerate a bottleneck baked deep inside Doxygen itself.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/flude-guide" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/flude-guide&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>branding</category>
      <category>cloudflare</category>
      <category>story</category>
      <category>flude</category>
    </item>
    <item>
      <title>Breaking Down Our Pipeline: From a Symlink Hack to Five Independent Deployments</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Fri, 04 Sep 2026 08:01:20 +0000</pubDate>
      <link>https://dev.to/flude_team/breaking-down-our-pipeline-from-a-symlink-hack-to-five-independent-deployments-1mjc</link>
      <guid>https://dev.to/flude_team/breaking-down-our-pipeline-from-a-symlink-hack-to-five-independent-deployments-1mjc</guid>
      <description>&lt;p&gt;Flude's documentation pipeline survived for years the same way most internal tooling does. It just worked. Nobody in their right mind wanted to touch it. The &lt;code&gt;engine&lt;/code&gt; package generated its API reference using a clunky symlink hack we mentioned in the &lt;a href="https://dev.to/en/blog/escaping-to-self-hosted-runners/"&gt;escape-to-GitHub-Actions episode&lt;/a&gt;. Platform constraints forced our hand here. Free GitHub Pages limits site publishing to public repositories only. The engine's source code absolutely had to stay private. We bypassed this by running &lt;code&gt;ln -s ../engine ./user-docs/engine&lt;/code&gt; during the CI build. The public &lt;code&gt;user-docs&lt;/code&gt; repo quietly built a site out of someone else's code. Flude processed those files on the fly to push the final static result out the door.&lt;/p&gt;

&lt;p&gt;That duct-tape setup actually got the job done. It also created a concrete wall of architectural coupling. Publishing a single line of API documentation required the private engine to leak its source into a public neighbor. The &lt;code&gt;user-docs&lt;/code&gt; project got stuck carrying the weight of an external build process. We couldn't build or test the engine's docs in isolation. The entire toolchain only triggered from inside the documentation repo. Every single run pulled down megabytes of fresh code. The &lt;code&gt;design-docs&lt;/code&gt; project suffered from the exact same disease on a smaller scale. Its GitHub Pages deployment ran centrally out of the giant &lt;code&gt;Pipeline&lt;/code&gt; umbrella repo. It relied on an SSH deploy key to punch through into a completely different repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding a host that asks fewer questions
&lt;/h2&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%2Fox5o3pr7phj0xz0h7r1j.jpg" 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%2Fox5o3pr7phj0xz0h7r1j.jpg" alt="Five repositories instead of one" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We weighed two escape routes for the engine. One option involved shipping the parsed intermediate representation as a versioned artifact like a wheel or GitHub Release. Downstream projects could grab that package and render the docs locally. That sounds clean on paper. In reality, it introduces a nightmare of schema versioning we desperately wanted to avoid. The brute-force approach won out in the end. The engine now parses, renders, and deploys its own reference from its own CI pipeline. Zero intermediate contracts. Zero artifact publishing rituals.&lt;/p&gt;

&lt;p&gt;We still had to bypass the old GitHub Pages restriction. The platform demanded a public repo—so we ditched the platform. Cloudflare Pages offers a free tier that deploys finished build artifacts while completely ignoring repository visibility. The engine finally publishes its own reference while staying hidden from the world. The actual migration required nothing more than copying &lt;code&gt;Doxyfile&lt;/code&gt;, &lt;code&gt;hugo-site&lt;/code&gt;, and &lt;code&gt;sidebar.toml&lt;/code&gt; directly into the engine repository. The generated &lt;code&gt;content/&lt;/code&gt; and &lt;code&gt;public/&lt;/code&gt; directories went straight into &lt;code&gt;.gitignore&lt;/code&gt;. They no longer haunt our commit history as static fixtures.&lt;/p&gt;

&lt;p&gt;We rolled this exact logic down the rest of the chain. Both &lt;code&gt;design-docs&lt;/code&gt; and &lt;code&gt;user-docs&lt;/code&gt; migrated to Cloudflare Pages. Each project received a dedicated &lt;code&gt;deploy.yml&lt;/code&gt; workflow. GitHub Actions provides native read access to the runner's repository, making deploy keys obsolete. The old &lt;code&gt;cloudflare/pages-action@v1&lt;/code&gt; had already been archived by the community. We skipped the legacy tooling and moved all five repositories straight to the supported &lt;code&gt;wrangler-action&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One token to rule them all
&lt;/h2&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%2F0zrthg6vl4mvgmmc1gv9.jpg" 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%2F0zrthg6vl4mvgmmc1gv9.jpg" alt="Token Rotation" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our perfectly clean plan slammed into a brick wall of reality right about here. Every deployment requires a &lt;code&gt;CF_API_TOKEN&lt;/code&gt;. The textbook reaction is generating a narrowly scoped token with least privilege for each repository. Cloudflare violently disagrees with this approach. You cannot scope an API token to a single Pages project. They grant permissions across the entire account—or nothing at all. Any "narrow" token we issued would instantly gain full access to our entire infrastructure. Worse yet, Cloudflare displays the token value exactly once during creation. If you miss it, you have to roll the credential entirely.&lt;/p&gt;

&lt;p&gt;The rollout process turned into a timed sprint. We roll the token once. Then we distribute the new string across five repositories within a few frantic minutes. Everything happens through an interactive prompt. Secrets never touch a script, a file on disk, or a Slack message. Five independent repositories now share a single token—and a single rotation window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auditing the dead code
&lt;/h2&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%2Fxp5erooa7zs6cxia6euz.jpg" 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%2Fxp5erooa7zs6cxia6euz.jpg" alt="CI Bill Audit" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the deployments turned green, we had to ask ourselves a tough question. How much of this machinery was actually doing anything useful? The audit unearthed a pile of historical garbage. A &lt;code&gt;repository_dispatch&lt;/code&gt; trigger from every submodule used to restart integration tests in the umbrella repo. It fired before the gitlink even bumped. The CI was aggressively testing a state that hadn't changed. We gutted that nonsense and replaced it with a simple six-hour schedule on the bump job itself. We lost absolutely nothing in speed because those PRs never auto-merged anyway.&lt;/p&gt;

&lt;p&gt;The language test matrix in the umbrella repo turned out to be a carbon copy of the engine's internal CI. The original version even enforced a stricter coverage threshold. A job designed to verify the built site was quietly testing an architecture that died months ago. It merged Hugo's output with a VitePress tree to check cross-links. We had long since split them into separate Cloudflare Pages projects on different domains. The only connection left is a standard external link. You never spot these ghosts reading a clean architecture diagram. They only surface when you dig through the raw runner logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Severing the final tie
&lt;/h2&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%2Fagqoqa5zqxzt77ea1yop.jpg" 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%2Fagqoqa5zqxzt77ea1yop.jpg" alt="Cutting the Last Dependency" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One tiny cross-repository hook remained. The failure-alert step in every workflow dynamically pulled a shared Telegram notifier from &lt;code&gt;Pipeline&lt;/code&gt; at runtime. That single step required another authentication token. A supposedly independent repository was still reaching back to daddy for help. We copy-pasted that action file into every single project. Maintaining five copies instead of one shared source is the real cost of independence.&lt;/p&gt;

&lt;p&gt;You are reading this blog on a site built with the exact same pattern. It uses self-deployment with zero external deploy keys. It runs its own copy of the notifier. It lives on that exact same Cloudflare architecture.&lt;/p&gt;

&lt;p&gt;Next time, we'll explain how we hunted down a public name for this documentation. The original working title turned out to be locked behind corporate lawyers, sending us down a rabbit hole of weird domain names.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/decomposing-the-pipeline-monorepo" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/decomposing-the-pipeline-monorepo&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>cicd</category>
      <category>cloudflare</category>
      <category>story</category>
    </item>
    <item>
      <title>The 'if not 0:' Easter Egg: How Our AI Audit Found Dead Code in the Orchestrator</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Tue, 01 Sep 2026 05:12:56 +0000</pubDate>
      <link>https://dev.to/flude_team/the-if-not-0-easter-egg-how-our-ai-audit-found-dead-code-in-the-orchestrator-2ii0</link>
      <guid>https://dev.to/flude_team/the-if-not-0-easter-egg-how-our-ai-audit-found-dead-code-in-the-orchestrator-2ii0</guid>
      <description>&lt;p&gt;We keep exposing the internals of our project without embellishment. Recently, we launched an autonomous AI audit across the entire engine core. The agent was tasked with cleaning up type hints, fixing docstrings, and deleting dead code. Living humans rarely get around to this kind of mundane technical hygiene.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Find
&lt;/h2&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%2Famfnwzhty0unbo5vpanb.jpg" 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%2Famfnwzhty0unbo5vpanb.jpg" alt="An Easter egg in the code" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent systematically combed through the files and stumbled inside &lt;code&gt;ude/orchestrator.py&lt;/code&gt;. Inside the &lt;code&gt;load_sidebar_toml&lt;/code&gt; function, it unearthed a fascinating piece of logic. We stared at the screen and couldn't quite believe our eyes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;toml_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sidebar.toml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;toml_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;UdeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Required sidebar.toml not found in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;doc_dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Every document directory must contain a sidebar.toml file.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero in Python is interpreted as falsy. The expression &lt;code&gt;not 0&lt;/code&gt; always evaluates to true. The outer condition simply forced an extra indentation level and was guaranteed to execute every single time. It was a useless wrapper that stole a line in the stack trace from anyone trying to debug the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Got There
&lt;/h2&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%2Fd3t5518t98xy921spyhy.jpg" 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%2Fd3t5518t98xy921spyhy.jpg" alt="Dead Code in Pull Request" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A quick &lt;code&gt;git blame&lt;/code&gt; revealed the author and the creation time. The line sneaked in via PR #56 ("Feature/section 2.1 orchestrator library api"), part of a massive orchestrator refactoring. The developer temporarily disabled the block using &lt;code&gt;if 0:&lt;/code&gt; for a quick local debugging session. They flipped the logic back with a negation, committed the changes, and forgot to remove the wrapper before the merge. The test suite happily passed. The behavior of the code remained completely unchanged.&lt;/p&gt;

&lt;p&gt;This construct didn't break any functionality or open security vulnerabilities. It simply added pure cognitive noise. Everyone reading this file would freeze for a second, trying to decipher the sacred meaning behind the check. That extra second of confusion accumulates into hours of wasted time across thousands of lines of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&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%2Fgsi3ew1iw5vmwcfeiyy4.jpg" 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%2Fgsi3ew1iw5vmwcfeiyy4.jpg" alt="A minimal fix with zero behavior change" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;toml_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sidebar.toml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;toml_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;UdeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Required sidebar.toml not found in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;doc_dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Every document directory must contain a sidebar.toml file.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We deleted the wrapper and shifted the code to the left. The full test suite ran clean—731 passed, 0 failed, coverage holding at 98.12%. The system's behavior hasn't shifted a millimeter. Regular automated auditing pays off exactly through these tiny cleanups. It sweeps out harmless garbage in bulk while you focus on complex challenges. Speaking of complex challenges. Next time, we'll break down exactly how we sliced our massive pipeline monorepo into five fully independent deployments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/stale-conditional-easter-egg" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/stale-conditional-easter-egg&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>postmortem</category>
      <category>aiagents</category>
      <category>codequality</category>
      <category>python</category>
    </item>
    <item>
      <title>Typed Models: How we stopped silently losing data</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Fri, 28 Aug 2026 04:41:52 +0000</pubDate>
      <link>https://dev.to/flude_team/typed-models-how-we-stopped-silently-losing-data-2d5e</link>
      <guid>https://dev.to/flude_team/typed-models-how-we-stopped-silently-losing-data-2d5e</guid>
      <description>&lt;p&gt;The first version of Flude's intermediate representation was built quick and dirty. We shoved classes, interfaces, enumerations, and variables into a single bottomless template. This universal model had a basic text field. It simply dictated what the object was pretending to be at any given moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The leak nobody knew about
&lt;/h2&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%2Fell59rou4f7kj22f97be.jpg" 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%2Fell59rou4f7kj22f97be.jpg" alt="One class for all occasions" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Class fields were stored as a flat list of strings. We lost types, documentation, and access specifiers, keeping only the bare variable names. The parsers honestly extracted all this rich information from the source code. Our supermodel grabbed the name and trashed the rest. There were no exceptions and absolutely zero warnings in the logs. The renderer downstream physically couldn't show the type in the documentation. It received a string and manipulated a string.&lt;/p&gt;

&lt;p&gt;Generation ran successfully, and files were created. Developers read the reference and tried to guess the data types. Silent degradation is the worst possible scenario when building developer tools. The system lies to your face, pretending everything is completely fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strict models
&lt;/h2&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%2Ft177dv2nyd5py3venzwp.jpg" 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%2Ft177dv2nyd5py3venzwp.jpg" alt="Strict data architecture" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We trashed the old code. Separate structures emerged for classes, methods, enumerations, and parameters. A class field became a fully typed object that guaranteed the presence of its name, specific type, and documentation. The transition hurt. Any legacy code that habitually accessed fields as text crashed immediately. The system finally started screaming about mismatches. We scrubbed out old string-based access patterns and rewrote the generation templates. In return, we got a solid guarantee: if the parser finds a complex type, it will absolutely make it to the final page intact.&lt;/p&gt;

&lt;p&gt;Typing kills an entire layer of silent bugs. Strict contracts force you to write predictable code and free up mental space since you stop memorizing custom formats. Speaking of cleaning up technical debt. In the next post, we'll show how our AI audit dug into the orchestrator code and found an absolute masterpiece—a stale &lt;code&gt;if not 0:&lt;/code&gt; condition left over from a local debugging session.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/typed-entity-models" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/typed-entity-models&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>pydantic</category>
      <category>python</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>Developer Guides: We didn't abandon the guides, just Flude—and then changed our minds</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Tue, 25 Aug 2026 05:44:46 +0000</pubDate>
      <link>https://dev.to/flude_team/developer-guides-we-didnt-abandon-the-guides-just-flude-and-then-changed-our-minds-o7b</link>
      <guid>https://dev.to/flude_team/developer-guides-we-didnt-abandon-the-guides-just-flude-and-then-changed-our-minds-o7b</guid>
      <description>&lt;p&gt;We promised to cover the integration of Developer Guides in the previous article. We changed our approach to building these guides twice. We initially moved them completely outside the Flude generator. Harsh reality eventually forced us to reconsider that decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first attempt: monolithic HTML generator
&lt;/h2&gt;

&lt;p&gt;A single clunky tool built both the API reference and the guides on the old HTML stack. The guides covered standard scenarios like quick starts and custom project development. They also included collections of articles detailing SDK-specific features. These topics relate to working with the platform as a whole and require dedicated text descriptions.&lt;/p&gt;

&lt;p&gt;We blindly carried this monolithic integration over into the first version of Flude. The old workflow seemed perfectly fine. The subject matter of the guides remained exactly the same, so nobody thought about decoupling the processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second attempt: the illusion of plain Markdown
&lt;/h2&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%2F3g95miz43d8tyqoxb7l8.jpg" 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%2F3g95miz43d8tyqoxb7l8.jpg" alt="Guides left on the side of the road" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We decided to remove the guides from Flude's jurisdiction during the transition to a static site generator. The logic felt rock-solid. We wanted our parser to handle the complex API extraction. Handwritten guides are just standard Markdown text that the static generator can assemble on its own. The initial translation from HTML required minimal automation.&lt;/p&gt;

&lt;p&gt;The guides moved into a separate directory—the SSG picked them up directly without any preprocessing. Flude dropped out of this build step entirely. The separation looked great on paper since two independent content sources only met on the final page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Returning to a unified process
&lt;/h2&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%2Flj51uilt791sw14z1heu.jpg" 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%2Flj51uilt791sw14z1heu.jpg" alt="Broken links between systems" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We eventually noticed a massive navigation problem. The guides knew absolutely nothing about the API reference pages. The reference had no idea the guides existed. Links to classes or methods led nowhere if the target page hadn't generated yet. They also died quietly when paths changed during a rebuild. Neither process could see the other to flag the broken connection.&lt;/p&gt;

&lt;p&gt;We brought Developer Guides back under Flude's control using a different approach. The engine no longer renders them directly. It now validates the file formatting and tracks the integrity of cross-references. The SSG still handles the final portal assembly. Flude integrates into this pipeline as a dedicated validation step. Both systems operate within a unified workflow that catches broken links before publication.&lt;/p&gt;

&lt;p&gt;Clean architectural separation often crashes against real-world requirements. Splitting systems apart inevitably requires building reliable bridges between them. The next article explores the Typed Models that helped us organize scattered data into a predictable structure.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/developer-guides-return" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/developer-guides-return&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>documentation</category>
      <category>ssg</category>
      <category>story</category>
    </item>
    <item>
      <title>Regression Testing: Proving Your Code Doesn't Lose Data</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Thu, 20 Aug 2026 13:53:02 +0000</pubDate>
      <link>https://dev.to/flude_team/regression-testing-proving-your-code-doesnt-lose-data-237a</link>
      <guid>https://dev.to/flude_team/regression-testing-proving-your-code-doesnt-lose-data-237a</guid>
      <description>&lt;p&gt;Building a working prototype for yourself is fairly easy. Pushing that rogue software into the official processes of a massive corporation is much harder. We finished developing the core logic and had to defend the project in front of management. People were used to the old generator. It spent hours building documentation while remaining predictable and providing strict control over code coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wall of Distrust
&lt;/h2&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%2Fur7b1u0nv413pydsis0f.jpg" 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%2Fur7b1u0nv413pydsis0f.jpg" alt="Comparing architectures: the old combine vs the new engine" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our engine relied on a completely different architecture. We dreamed of writing our own lightweight parsers. Tight deadlines forced us to grab Doxygen instead. It runs slowly and barely caches anything, but it consistently produces stable XML. Our code ingests this format, processes it into a custom data model, and renders clean Markdown.&lt;/p&gt;

&lt;p&gt;Management looked at this layered setup with deep skepticism. They asked us directly for guarantees that the third-party parser wouldn't skip half the classes in our complex C++ codebase. Promises were useless. We needed hard numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Head-to-Head Collision
&lt;/h2&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%2Fjew6cl2s6wc7uwqagok3.jpg" 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%2Fjew6cl2s6wc7uwqagok3.jpg" alt="Dashboard with coverage metrics and match percentages" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We wrote a regression testing script to directly compare the old corporate standard against our new Flude generator. We ran these tests across 36 combinations of SDKs and programming languages, including C++, C#, and Python.&lt;/p&gt;

&lt;p&gt;The approach involved extracting the exact count of classes, methods, and enumerations from the legacy documentation. We then cross-checked those numbers against the output of our engine. All the data flowed into a single dashboard. We monitored the baseline metrics, our findings, and the final match percentage. The dashboard also listed missing entities and accidentally included garbage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Battling the Discrepancies
&lt;/h2&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%2Fx2hoid16ujdizfue549i.jpg" 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%2Fx2hoid16ujdizfue549i.jpg" alt="Scrubbing utility junk from the code" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The initial runs produced tons of inconsistencies. Doxygen choked on complex C++ template constructs and generated empty files instead of actual code. Internal autogenerator classes leaked out in the C# and Python wrappers—classes which the old tool had silently hidden.&lt;/p&gt;

&lt;p&gt;We started the tedious grind of writing on-the-fly preprocessors. We stripped out problematic code blocks before parsing even started. We invented merge rules for anonymous enumerations and scrubbed utility trails. Every tweak plugged another hole in the metrics. We methodically adjusted the results until the match percentage reached 99.9-100% across all 36 combinations.&lt;/p&gt;

&lt;p&gt;The final numbers proved that the Flude and Doxygen combination found the exact same tens of thousands of entities as the old standard. Nobody gave us the green light immediately. We simply validated the concept for ourselves. Our documentation includes detailed Developer Guides alongside the API references. The next article covers how we integrated those guides into the new generator.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/regression-testing" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/regression-testing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>enterprise</category>
      <category>story</category>
      <category>flude</category>
    </item>
    <item>
      <title>Guerrilla DevOps: How We Escaped to GitHub Actions</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Mon, 17 Aug 2026 17:05:56 +0000</pubDate>
      <link>https://dev.to/flude_team/guerrilla-devops-how-we-escaped-to-github-actions-19k5</link>
      <guid>https://dev.to/flude_team/guerrilla-devops-how-we-escaped-to-github-actions-19k5</guid>
      <description>&lt;p&gt;In large companies, processes eventually take on a life of their own and start calling the shots. For us, the stumbling block was documentation publishing. Previously, technical writers would spend the entire month agonizingly collecting edits, only to push a single manual release out the door. That routine drove me up the wall. I wanted a proper CI/CD setup where everything builds itself after every push. But simply wanting it wasn't enough—I had to clearly prove to my colleagues that automation actually works in practice.&lt;/p&gt;

&lt;p&gt;Transitioning to Static Site Generators (SSGs) required a Markdown parser. Our old internal tool could only spit out XML. Worse, on large codebases, it would grind to a complete halt for hours. Fortunately, while suffering through that, we had already put together a working &lt;code&gt;Python ➡️ HTML&lt;/code&gt; pipeline (which I talked about in the previous part). All we had to do was hack it into &lt;code&gt;Python ➡️ Markdown&lt;/code&gt; and start generating documentation for Flude itself. After all, the engine was also written in Python.&lt;/p&gt;

&lt;p&gt;Next, infrastructure became the bottleneck. And that's when the corporate gears started grinding. Our Infrastructure Lead (the very guy who pitched the SSG idea) was buried under a mountain of tasks and then went on vacation. Sit and wait for his return just to fight for resources on the internal server? That meant putting the project on ice for at least a couple of months. So, I decided to go rogue.&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%2Fcjxf687v6ftcp6wtnnwt.jpg" 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%2Fcjxf687v6ftcp6wtnnwt.jpg" alt="Corporate Gears" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Escape Plan
&lt;/h2&gt;

&lt;p&gt;If the internal network is off-limits, we take the experiment outside. GitHub fit the bill perfectly. I hid the engine's source code and the requirements list in private repositories. But the User Docs and the API Reference portals were quietly deployed in the cloud. At the end of the day, this was the open-source Flude. We weren't exposing a single line of proprietary corporate code.&lt;/p&gt;

&lt;p&gt;Since we were building the project from scratch and for ourselves, we decided to experiment a bit. We picked completely different SSG frameworks for different portals. This complicated things slightly, but it was a great way to cut our teeth on configuring a diverse tech stack.&lt;/p&gt;

&lt;p&gt;The pipeline architecture turned out exceptionally elegant. The User Docs portal automatically pulled fresh code from the closed repository. Then Flude woke up and parsed the source into Markdown on the fly. The SSG picked up the ready markup, built a static site out of it, and immediately pushed it to hosting (GitHub can serve HTML pages from public repos for free).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Blind Architect
&lt;/h2&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%2F5w4onlpmvvkb16ybe628.jpg" 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%2F5w4onlpmvvkb16ybe628.jpg" alt="The Blind Architect" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In theory, I understood the mechanics of CI/CD perfectly. The problem was purely practical: I didn't know the first thing about YAML syntax and had never configured GitHub Actions in my life. Just like when I started with Python, I was flying blind as an architect once again.&lt;/p&gt;

&lt;p&gt;My AI teammate took over all the grunt work. I just described in plain English what should happen at each step. In return, the neural network spit out ready-made files for the &lt;code&gt;.github/workflows&lt;/code&gt; folder. Sometimes it even slapped my wrist and forced me to implement best practices (like setting up dependency caching or splitting monolithic tasks into separate jobs). We pushed configs, stared at the red logs of failed builds, swore, fixed bugs, and ran it all over again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment of Truth
&lt;/h2&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%2F4gulywoh82efnupf3m0f.jpg" 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%2F4gulywoh82efnupf3m0f.jpg" alt="Green Checkmark" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a few evenings of non-stop tinkering, I made another push. GitHub Actions kicked into gear. The repository downloaded. Dependencies installed. Flude ran through the code, spitting out Markdown. The SSG instantly compiled the HTML. Deployment passed.&lt;/p&gt;

&lt;p&gt;A green checkmark finally lit up on the screen. I clicked the site's URL and saw fresh documentation. It had updated itself, completely without my involvement.&lt;/p&gt;

&lt;p&gt;At that moment, I screamed "Yes! Yes! Yes! It works!" so loudly that I scared my family, the budgies, and a couple of neighbors. It's an amazing feeling. Your concept just came to life right on the monitor. A routine monthly release just shrank down to a two-minute automated process.&lt;/p&gt;

&lt;p&gt;We elegantly bypassed the corporate infrastructure, and the guerrilla pipeline was running like a well-oiled machine. But the final boss was looming ahead—legalizing the tool inside the company. We had to prove to the security department and management that our homegrown generator was just as good as the old standard. How do you run regression testing on closed data and make management believe in our parser? We'll cover that in the next episode.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/devops-on-github" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/devops-on-github&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>githubactions</category>
      <category>story</category>
      <category>flude</category>
    </item>
    <item>
      <title>The Markdown Singularity: Why Generating HTML is a Dead End</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Thu, 13 Aug 2026 14:27:54 +0000</pubDate>
      <link>https://dev.to/flude_team/the-markdown-singularity-why-generating-html-is-a-dead-end-1n08</link>
      <guid>https://dev.to/flude_team/the-markdown-singularity-why-generating-html-is-a-dead-end-1n08</guid>
      <description>&lt;p&gt;At the end of the previous part, we were sitting on a perfectly working, fully tested pipeline. Our converter successfully chewed through Doxygen XML and generated custom HTML pages for our API documentation. The team was already getting ready to pop the champagne and push everything to production when the Infrastructure Lead walked over. He asked one simple, yet absolutely devastating question: "Why are you still messing around with HTML generation when off-the-shelf tools like Hugo, Docusaurus, or VitePress exist?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pure HTML Trap
&lt;/h2&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%2Fihp9hkb3ejpttox3kfij.jpg" 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%2Fihp9hkb3ejpttox3kfij.jpg" alt="HTML Trap" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It took us exactly a minute to realize the sheer scale of our mistake. Generating pure HTML sounds great right up until you try to build a modern developer portal around it. It turned out we were about to reinvent the wheel for practically every standard web feature. Global search would require writing our own indexer. For navigation, we'd have to manually maintain a consistent sidebar across hundreds of generated pages, and synchronizing our styles with the corporate CSS threatened to turn into an endless chore. Instead of a documentation system, we had somehow started writing our own web framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SSG Awakening
&lt;/h2&gt;

&lt;p&gt;Static Site Generators (SSGs) like Hugo or Docusaurus solve all these problems out of the box. They come with built-in search, routing, and plugin systems. The only catch was that they strictly eat Markdown as input. We had to make a sharp pivot and re-engineer our pipeline from &lt;code&gt;Doxygen XML ➡️ HTML&lt;/code&gt; to &lt;code&gt;Doxygen XML ➡️ Markdown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rewriting the output layer was surprisingly painless. Even during the HTML generation phase, we had strictly separated the parsing and rendering logic, introducing an intermediate data layer we called the Intermediate Representation (or simply IR). This guaranteed the parser's stability while we ripped out the HTML generator and bolted on Markdown instead. And the practice of TDD definitely played its part—thanks to our tests, the Markdown generator was written quickly and confidently. As we worked on the new exporter, the puzzle pieces suddenly clicked together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Engine Singularity
&lt;/h2&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%2Fqjuhfki5puw49xwme4w0.jpg" 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%2Fqjuhfki5puw49xwme4w0.jpg" alt="Flude Architecture" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our architecture now consisted of two isolated phases. First, the Parser reads the source code and builds an abstract tree (that same IR), and then the Renderer takes the finished tree and translates it into Markdown. That’s when it hit us: the renderer absolutely does not care where this data came from.&lt;/p&gt;

&lt;p&gt;We recalled the situation from the second article, when the AI tried to throw out Doxygen and started parsing the Python code directly via &lt;code&gt;tree-sitter&lt;/code&gt;. Back then, we panicked about losing control and rolled back to parsing XML (even though the AI offered a working solution). Now, with a strict TDD contract and the intermediate IR layer in place, there was nothing to fear. We could safely ditch Doxygen and pull data directly from the source code.&lt;/p&gt;

&lt;p&gt;Using an IR also came with the bonus of caching and faster generation times. If we fully build the tree in memory on the first run, there is no reason to rebuild it entirely on the next pass. We can simply use incremental parsing and update only the chunks that actually changed. And if we can read Python directly, nothing stops us from reading our main C++ SDK and our other languages. Or maybe even—dare I say it—languages that aren't ours.&lt;/p&gt;

&lt;p&gt;This was the exact moment our highly specialized script mutated into a full-fledged Universal Documentation Engine (UDE). We did have to lengthen the name a bit due to an existing trademark for a German debugger called Universal Debug Engine. We tacked on the characteristics Fast and Layered, and thus Flude was born. Now we can plug any parser into the frontend, and the pipeline's backend will assemble beautiful Markdown for our SSG (complete with unified search and a proper design).&lt;/p&gt;

&lt;h2&gt;
  
  
  From a Local Task to a Massive Project
&lt;/h2&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%2Fmi05jqtjqi7zpve3wdoz.jpg" 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%2Fmi05jqtjqi7zpve3wdoz.jpg" alt="Scaling" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A tiny local utility for a couple of Python modules suddenly gave us the chance to design a scalable architecture from scratch. Building a product like this meant we could no longer rely on manual builds or running scripts locally on our laptops. We needed a fully automated CI/CD process.&lt;/p&gt;

&lt;p&gt;I didn't have to figure this out by myself—my AI teammate took on the lion's share of the setup work. In the next episode, I'll explain why we decided to build our pipelines specifically on GitHub Actions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/the-markdown-singularity" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/the-markdown-singularity&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>story</category>
      <category>architecture</category>
      <category>ssg</category>
      <category>flude</category>
    </item>
    <item>
      <title>TDD as a Leash for AI: Writing Tests Without Knowing the Language</title>
      <dc:creator>Flude team</dc:creator>
      <pubDate>Mon, 10 Aug 2026 14:23:43 +0000</pubDate>
      <link>https://dev.to/flude_team/tdd-as-a-leash-for-ai-writing-tests-without-knowing-the-language-2f5m</link>
      <guid>https://dev.to/flude_team/tdd-as-a-leash-for-ai-writing-tests-without-knowing-the-language-2f5m</guid>
      <description>&lt;p&gt;At the end of the previous article, our project hit a dead end. The neural network had quietly plugged in the abstract syntax tree parser &lt;code&gt;tree-sitter&lt;/code&gt;, turning the codebase into an absolutely unreadable "black box" for me.&lt;/p&gt;

&lt;p&gt;We tried to force it to roll back the changes, but bugs started crawling through the generated code. Dependencies broke, and the script stopped running. Assessing the scale of the disaster, I came to a simple but harsh conclusion: &lt;strong&gt;it is much easier to write everything from scratch than to try to fix the AI's architectural glitches, especially given my complete lack of Python knowledge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So our second prototype went into the trash can. We needed a new approach that wouldn't allow the neural network to act on its own. This tight leash became an uncompromising combination: a detailed formulation of requirements and total automated testing.&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%2Fgkof13elzso5vnzl6gw2.jpg" 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%2Fgkof13elzso5vnzl6gw2.jpg" alt="Contract with AI" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Contract with AI
&lt;/h2&gt;

&lt;p&gt;The problem with previous iterations was that we set tasks like "parse this file and make it pretty". This gave the AI too much freedom.&lt;/p&gt;

&lt;p&gt;To bring the architecture under control, we completely changed our workflow. Before writing a single line of code, we now established a strict "contract" with the neural network. Here is how it looked: first, we fixed strict requirements detailing permitted and forbidden libraries. Then, the AI generated a step-by-step plan for my approval. And most importantly, not a single function was written without a pre-created automated test.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Write Tests If You Can't Code?
&lt;/h2&gt;

&lt;p&gt;A logical question arises here: how can a system architect who has never written a single line of Python in his life implement Test-Driven Development (TDD)? The answer is simple — the AI writes the tests too.&lt;/p&gt;

&lt;p&gt;I didn't need to invent complex verification logic. I would take the original XML generated by Doxygen and find the required fragment there (for example, the description of a single class). Then I would take the beautiful, formatted HTML code that was supposed to be the final output (the exact one developers were used to in the old system).&lt;/p&gt;

&lt;p&gt;I would feed both pieces into the chat and set the task: &lt;em&gt;"Here is the input XML. Here is the final HTML that should be output. Write a test that takes this XML, runs it through our parser, and verifies that the output strings match this HTML character by character"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The neural network obediently wrote the test using &lt;code&gt;pytest&lt;/code&gt;. Naturally, upon the first run, it failed with an error (since the parser itself didn't exist yet).&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%2Fg7df1mwr42n5ix7aqvsj.jpg" 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%2Fg7df1mwr42n5ix7aqvsj.jpg" alt="TDD Cycle" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Red. Green. Refactor
&lt;/h2&gt;

&lt;p&gt;We took the classic TDD cycle and adapted it slightly for working with AI. When the test failed (&lt;strong&gt;Red&lt;/strong&gt;), the neural network caught the error in the console itself and immediately started debugging. Then came the &lt;strong&gt;Green&lt;/strong&gt; stage: the AI silently fixed the code, re-ran the check, and the test passed. We got a working piece of code doing exactly what was requested. Finally, if the neural network suddenly decided that the algorithm wasn't elegant enough and tried to rewrite it using regular expressions (&lt;strong&gt;Refactor&lt;/strong&gt;), I no longer panicked. We simply ran the tests again. If they were green — let it optimize as much as it wants, but at the slightest hint of red, we immediately rolled back the changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic of Isolation
&lt;/h2&gt;

&lt;p&gt;This approach worked flawlessly. The AI found itself strictly limited by our requirements and tests. It could no longer hallucinate non-existent parameters or quietly change the architecture, because any misstep caused the tests comparing the output HTML with the reference to fail.&lt;/p&gt;

&lt;p&gt;Development proceeded incredibly fast. We covered all scenarios with tests (classes, methods, enumerations, parameters). The AI wrote the code, ran the checks itself, and we steadily moved on. In the end, we had a perfectly working engine in our hands that took bulky XML from Doxygen and flawlessly turned it into stunningly beautiful HTML pages. The project could be safely deployed to production, if not for one overlooked detail.&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%2Fed2hl0tmn9muaqlpbgm5.jpg" 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%2Fed2hl0tmn9muaqlpbgm5.jpg" alt="Infrastructure Surprise" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next day, the Infrastructure Lead came to us and was genuinely surprised: "Why are you still fussing around with HTML when there are SSGs, from the beautiful Docusaurus to the super-powerful Hugo?".&lt;/p&gt;

&lt;p&gt;I will talk about how this single sentence forced us to rebuild half the project all over again in the next part.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on our blog: &lt;a href="https://blog.flude.guide/blog/tdd-as-a-leash" rel="noopener noreferrer"&gt;https://blog.flude.guide/blog/tdd-as-a-leash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also read us:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/ude_blog_en" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.flude.guide/rss-en.xml" rel="noopener noreferrer"&gt;RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>story</category>
      <category>architecture</category>
      <category>tdd</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
