<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Prelude Training: Tech Blog]]></title><description><![CDATA[This is for all things technical. Prelude offers technical training, primarily focused on software engineering, AI and Python. Prelude also produces e-learning software. ]]></description><link>https://preludetraining.substack.com/s/tech-blog</link><image><url>https://substackcdn.com/image/fetch/$s_!09MH!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92396c1f-7f0b-483b-8c1a-3d9c63175910_481x481.png</url><title>Prelude Training: Tech Blog</title><link>https://preludetraining.substack.com/s/tech-blog</link></image><generator>Substack</generator><lastBuildDate>Thu, 14 May 2026 08:29:42 GMT</lastBuildDate><atom:link href="https://preludetraining.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Sheena]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[preludetraining@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[preludetraining@substack.com]]></itunes:email><itunes:name><![CDATA[Sheena]]></itunes:name></itunes:owner><itunes:author><![CDATA[Sheena]]></itunes:author><googleplay:owner><![CDATA[preludetraining@substack.com]]></googleplay:owner><googleplay:email><![CDATA[preludetraining@substack.com]]></googleplay:email><googleplay:author><![CDATA[Sheena]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Agentic QA workflow]]></title><description><![CDATA[LLMs produce plausible code that slips past reviewers and unit tests. Here's how a QA plan and a Playwright-driven QA agent catch what they miss on the frontend]]></description><link>https://preludetraining.substack.com/p/quality-assurance-in-spec-driven</link><guid isPermaLink="false">https://preludetraining.substack.com/p/quality-assurance-in-spec-driven</guid><dc:creator><![CDATA[Sheena]]></dc:creator><pubDate>Fri, 24 Apr 2026 14:53:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!vl8f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vl8f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vl8f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png 424w, https://substackcdn.com/image/fetch/$s_!vl8f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png 848w, https://substackcdn.com/image/fetch/$s_!vl8f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!vl8f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vl8f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6488336,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://preludetraining.substack.com/i/195349355?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vl8f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png 424w, https://substackcdn.com/image/fetch/$s_!vl8f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png 848w, https://substackcdn.com/image/fetch/$s_!vl8f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!vl8f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d0f011f-ecfa-4f89-bc41-5ed4078debb2_2816x1536.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you&#8217;re building something small, single-use, or disposable with an LLM, you can cut corners. If you&#8217;re building something ambitious, you need guardrails.</p><p>Human code review, unit tests, linting, security scans, hooks, CI - the usual moves still help. A lot of it can be automated. But none of it covers everything. Especially not on the front-end.</p><h1>Why human review breaks down</h1><p>LLMs produce a lot of code. Fast. And the code they produce is usually very similar to working code - statistically close to the real thing. If you rely on human code reviewers to do a final pass on the code, you might be falling into a trap: LLM-generated code often looks plausible. You can read a hundred lines of working code and miss the one subtle thing that isn&#8217;t quite right. </p><p>And the better LLMs become at writing code, the harder it becomes to spot the subtly wrong. </p><p>Human reviewers can easily be set up to be accountability sinks, set up to fail. </p><p>Cory Doctorow has <a href="https://doctorow.medium.com/https-pluralistic-net-2025-12-05-pop-that-bubble-u-washington-8b6b75abc28e">an excellent essay</a> that goes further on this. Seriously worth a read if you&#8217;re planning to use human eyes as a quality gate in any AI-powered workflow.</p><p>That said, human attention is very valuable. The trick is to build systems that amplify and support that attention. Not systems that expect humans to have machine-like attention spans.</p><h1>A picture is worth a thousand words</h1><p>If you are building something with a modern web frontend, the way it looks and feels is pretty hard to test. You can easily put a lot of effort into making automated tests for your frontend, and then miss the fact that it sucks to use on mobile devices because the text is really, really tiny. </p><p>Often, problems in code are not very visible at all. They only appear when you interact with the front end. You look at the thing with your eyes, click around, and find the things that don&#8217;t quite do what you expected.  </p><p>Of course, automated tests help, but only so far. Playwright can check that buttons do what they&#8217;re supposed to when clicked. It can verify that menus appear and elements exist. But there is a limit. </p><p>If you build something with a frontend, you'd best look at the frontend. </p><p>Some teams have QA engineers who handle this kind of work, but it can become a pretty big, repetitive task. Manually clicking through the front end every time you want to ship is expensive in time and patience. You can&#8217;t rely on this kind of thing fully, you really shouldn&#8217;t, but it&#8217;s an important part of the toolbox.</p><h1>Quick primer on spec-driven development</h1><p>This post is about how I build QA functionality into my spec-driven development workflow. I realise that not everyone has heard of spec-dd, so here is a lil primer:</p><p>Spec-driven development means you don&#8217;t jump straight to code. First, you work with an LLM to write a spec &#8212; a markdown description of what needs to be built. Then you turn the spec into a plan &#8212; a concrete sequence of steps for implementing it, with references to files, tests, and code. Then you execute the plan.</p><p>A well-known example is <a href="https://github.com/github/spec-kit">spec-kit</a>.  I encourage you to go look at it very closely. But don&#8217;t assume it&#8217;ll work for you on your project by default.  I&#8217;ve played around with a few Spec DD implementations and couldn&#8217;t get one that fitted my needs. So I decided to make my own. </p><p>I recommend you do too.</p><h1>Where QA fits in</h1><p>In the workflow I&#8217;m currently using, an LLM is used to create an implementation plan AND a QA plan from a spec file. </p><p>A QA plan is a markdown file. It&#8217;s a list of steps a competent human QA could follow to verify that everything works. It&#8217;s a write-up of a bunch of manual click-tests that explain things like:</p><ul><li><p>Which pages to visit</p></li><li><p>Which buttons to click</p></li><li><p>Which menus to explore</p></li><li><p>Which tables should exist, and what they should contain</p></li><li><p>What to check on different screen sizes (full end-to-end on a large screen, visual-only spot-checks on smaller ones? )</p></li></ul><p>It&#8217;s written alongside the spec and the implementation plan, not after the code is done. It&#8217;s part of the design.</p><p>I can look at this QA plan and make sure it captures what the feature is actually meant to do &#8212; complicated user flows, edge cases, all the weirdness. I can use it to see if any weird assumptions have slipped in; it lets me see the LLM&#8217;s &#8220;understanding&#8221; of a feature before it gets built.</p><h1>Running the QA plan</h1><p>Once the code is in place and looks about right, there is a  command to run the QA test plan.</p><p>The command ensures the dev server is up, then passes the plan to a <strong>QA agent</strong>. The QA agent uses Playwright MCP to drive the browser &#8212; clicking, typing, navigating through the pages &#8212; and takes screenshots as it goes. The screenshots land in the same directory as the plan.</p><p>If a step needs data in the database &#8212; say, a feature that touches a large cohort of learners &#8212; the QA agent asks a <strong>QA helper</strong> subagent to make it. The helper has access to the dev database, and it can CRUD whatever it needs to.  </p><p>The QA helper subagent is encouraged to use existing factories to create the structures it needs, rather than writing long setup scripts in the codebase. I&#8217;m a big fan of <a href="https://factoryboy.readthedocs.io/en/stable/">factory_boy</a> for this: it lets you create a bunch of related objects at once, and the factories end up doubling as documentation for how your models relate.</p><p>I already have a bunch of factories set up that are used in the unit tests. The QA helper subagent uses those same factories. The object hierarchies it creates are officially sanctioned rather than hacky.</p><h1>You are a human&#8230;</h1><p>There&#8217;s one constraint on the QA agent that&#8217;s really important: <strong>it needs to behave like a human QA tester. </strong> It should not write code, run automated tests, fiddle with the database directly or any of that stuff. It can only interact with the browser as a human would.</p><p>If you let it act like a developer, it will cheat. It will write QA tests instead of clicking around. It will run the existing test suite and call that QA. It will add tests that duplicate what&#8217;s already in place, creating rigidity and coupling, and slowing down the whole suite. All of this is faster than actually walking through a front-end and looking at screenshots, so it&#8217;s what it&#8217;ll reach for.</p><p>So tell it, plainly, that it&#8217;s a human who can&#8217;t write code. If it needs help setting up test data, it can ask the QA helper. Otherwise, its job is to walk the plan, take screenshots, and report what it sees.</p><p>This costs tokens, and it costs time. The benefits are worth it: actual pictures of how the feature behaves, across the screen sizes your users will hit, without you having to click through every page yourself. Often, the QA report surfaces problems that did not show up in the unit tests.  It&#8217;s really good at spotting issues, and it&#8217;s patient enough to check every screen and interaction thoroughly. </p><h1>The report</h1><p>When the QA agent is done, it writes a report. </p><p>The report covers every step in the plan &#8212; passed, failed, or partially passed &#8212; with the relevant screenshots alongside. And it flags extra things the agent noticed along the way. Regressions in unrelated parts of the UI. Bad decisions elsewhere that only became visible because the agent passed through, or bugs and edge cases you didn&#8217;t consider before.</p><p>The functionality under test worked great, the report might say &#8212; but it noticed a problem with something on the next page over. Free bonus bug reports.</p><p>You can find a bunch of <code>qa_report.md</code> files that this flow has generated <a href="https://github.com/preludetech/Freedom-LS/blob/main/spec_dd/3.%20done/2026-04-01_09%3A27_allow%20override%20icons/qa_report.md">here</a>. </p><h1>Dealing with QA test failures</h1><p>If the report flags a real problem with the thing we&#8217;re actually building, then we need to fix it. Usually, TDD is the way to go:</p><ul><li><p><strong>Red</strong>: write a test that fails because of the bug.</p></li><li><p><strong>Green</strong>: fix the bug so the test passes.</p></li><li><p><strong>Refactor</strong>: Never forget this</p></li></ul><p>Some minor issues can be dealt with directly, without the full TDD dance. But TDD is the default.</p><h1>The plan and report as project history</h1><p>The QA plan and the QA report both live in the repo as markdown files alongside the spec file. That matters more than it might sound. Old QA reports become useful reference documentation - they tell you how things are meant to behave from an end-user&#8217;s perspective. </p><p>Zoom out, and it&#8217;s true for any artefact of spec-driven development. Specs, plans, QA plans, QA reports &#8212; each one is a small record of what was built, why, and how it behaved.</p><h1>Want to learn more?</h1><p>I&#8217;m running a zero-to-hero spec-driven development course:<br><strong><a href="https://prelude.tech/upcoming_workshops/18/details">Spec Driven Django Development with Claude Code</a></strong></p><p>You need a solid set of foundational skills to build effective workflows for your projects. There isn&#8217;t a one-size-fits-all solution. So this course has 2 parts:</p><ol><li><p>An intense 2-day workshop that starts with the foundations and builds up to more advanced skills and techniques. You&#8217;ll get familiar with all sorts of tools and techniques, and you&#8217;ll see why they matter and where to use them</p></li><li><p>One month membership in an Agentic Django Mastermind group. The real learning will begin when you start taking your skills and applying them to real projects.  This mastermind group is a place to get continued support and to interact with peers on a similar journey to you. </p></li></ol><p>This space keeps changing. There is still a lot to discover.  We&#8217;re building a place of discovery and mutual support. </p><p> </p>]]></content:encoded></item><item><title><![CDATA[Write code for the next developer, even if they're a robot]]></title><description><![CDATA[Building code-harness plugins alongside products]]></description><link>https://preludetraining.substack.com/p/write-code-for-the-next-developer</link><guid isPermaLink="false">https://preludetraining.substack.com/p/write-code-for-the-next-developer</guid><dc:creator><![CDATA[Sheena]]></dc:creator><pubDate>Fri, 17 Apr 2026 07:57:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!aaJa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aaJa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aaJa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png 424w, https://substackcdn.com/image/fetch/$s_!aaJa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png 848w, https://substackcdn.com/image/fetch/$s_!aaJa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!aaJa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aaJa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5343027,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://preludetraining.substack.com/i/194490246?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aaJa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png 424w, https://substackcdn.com/image/fetch/$s_!aaJa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png 848w, https://substackcdn.com/image/fetch/$s_!aaJa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!aaJa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2e94b-3e7d-4692-b819-02ca14825774_2816x1536.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Here is a piece of advice that I got early in my career. It really stuck with me. It changed a lot about how I think about code: </p><p>Always code as though the next developer who&#8217;ll touch or use your code is your client. There is usually somebody paying for the development. There is usually an end client, but the next developer in the chain should be treated as though they are as important.</p><p>That next developer might be your future self. Or somebody else entirely.</p><p>These days, the next developer is likely either a coding harness or somebody making use of a coding harness.</p><p>The principle still holds. But &#8220;readable&#8221; now means a harness can parse it. &#8220;Usable&#8221; means a harness can set up the project, find the scripts, and follow the conventions without you babysitting it.</p><h1>What I&#8217;m building</h1><p>I&#8217;m happy to report that I&#8217;ve been writing a lot of code lately. I&#8217;ve been working on an LMS &#8212; a learning management system &#8212; called FreedomLS. It&#8217;s a Django project designed to be extended by other Django projects. Think Wagtail, not WordPress: highly flexible, but to really make it dance, you need software development skills.</p><p>Other Django projects install FreedomLS and build on top of it. Everything is configurable, everything is overridable. You can cut out entire sections of the application and wire up your own stuff.</p><h1>The problem: other projects need to understand yours</h1><p>FreedomLS is not a simple piece of tech. It has certain technical requirements, standards, and a certain shape. So when somebody installs FreedomLS in their own project, their coding harness needs to be FreedomLS-aware &#8212; it needs to know the conventions, the scripts, the way things hang together.</p><p>Working code is great and all, but if the coding harness doesn&#8217;t know how to work with it, then things can get hairy.</p><h1>My strategy: a plugin that ships with the code</h1><p>Inside the FreedomLS repo, I have a directory called <code>fls-claude-plugin</code>. This is where the magic is. It&#8217;s a Claude Code plugin that can be installed in any project that uses FreedomLS &#8212; including FreedomLS itself.</p><p>You can see the actual plugin <a href="https://github.com/preludetech/Freedom-LS/tree/main/fls-claude-plugin">here</a>. It&#8217;s still a work in progress, but most things are.</p><p>The plugin has an init command: <code>/fls:init</code> that sets up everything the harness needs to know. Here&#8217;s what that actually does:</p><ul><li><p><strong>Permissions:</strong> The plugin recommends certain permissions and merges them into the project&#8217;s <code>.claude/settings.json</code>. If you&#8217;re using a plugin that does this kind of thing, it&#8217;s on you to be careful and make sure you&#8217;re happy with what&#8217;s been added.</p></li><li><p><strong>Configuration</strong>: The plugin requires certain variables &#8212; the site name, default admin credentials for development, that kind of thing. The init script creates a <code>.claude/fls.md</code> config file for the concrete implementation. FreedomLS has two concrete implementations in development right now &#8212; Bloom and First Class &#8212; and they each have different configurations.</p></li><li><p><strong>CLAUDE.md updates</strong>: The init script updates the project&#8217;s <code>CLAUDE.md</code> with the basics of how the plugin works.</p></li><li><p><strong>Wrapper scripts</strong>: The plugin contains shell scripts for what FreedomLS always needs to do. But concrete implementations are usually complicated enough to need extra steps &#8212; things before, after, or around what the base script handles. So instead of asking people to edit the plugin scripts directly, FreedomLS creates wrapper scripts in the project root that call the plugin scripts. The wrapper is where those project-specific steps go.</p></li></ul><p>Here&#8217;s a concrete example. The plugin includes a script called <code>install_dev.sh</code> for getting a development machine set up &#8212; I use it whenever I launch a new git worktree so we can do work in parallel (I&#8217;ll talk about worktrees in another post). It looks like this:</p><p><code>#!/bin/sh</code></p><p><code>uv sync</code></p><p><code>npm i</code></p><p><code>npm run tailwind_build</code></p><p><code># Set up per-branch database</code></p><p><code>./dev_db_init.sh</code></p><p><code># Apply migrations</code></p><p><code>uv run manage.py migrate</code></p><p></p><p>When you run <code>/fls:init</code>, a wrapper <code>install_dev.sh</code> gets created in the project root:</p><p></p><p><code>#!/bin/sh</code></p><p><code># install_dev.sh &#8212; Generated by fls plugin init</code></p><p><code># PLUGIN_DIR is set during fls:init to where the plugin is installed</code></p><p><code># (e.g., &#8220;submodules/Freedom-LS/fls-claude-plugin&#8221;)</code></p><p><code>PLUGIN_DIR=&#8221;?&#8221;</code></p><p><code># === FLS base setup ===</code></p><p><code>&#8220;$PLUGIN_DIR/scripts/install_dev.sh&#8221; &#8220;$@&#8221;</code></p><p><code># === Project-specific setup ===</code></p><p><code># Whatever you want to do, you do here. Eg setting up some demo data in the development database.</code></p><p>The base script handles what FreedomLS always needs. The wrapper gives concrete implementations a place to add their own steps &#8212; initialising databases, creating demo data, whatever makes sense for that specific project.</p><p>When any Claude skill or command needs one of those scripts, it references the wrapper in the project root &#8212; <code>@install_dev.sh</code> rather than <code>$PLUGIN_DIR/install_dev.sh</code>.</p><h1>How concrete implementations pick it up</h1><p>For now, concrete implementations pull in FreedomLS as a git submodule &#8212; so the plugin lands as an actual folder on disk.</p><p>Each concrete project gets its own <code>claude.sh</code> launcher that points at the plugin directory. Run <code>/fls:init</code>, and the harness knows about the conventions, the scripts, the config. After that, you just use the plugin &#8212; same commands, same workflow, tuned to that project&#8217;s specific setup.</p><h1>Transferrable Tricks</h1><p>None of this is FreedomLS-specific.</p><p>If you&#8217;re writing a package or library that other developers depend on, a plugin like this makes your code more usable.</p><p>If you&#8217;re building a standalone tool &#8212; something with an API or a user interface that a coding harness, or other AI-based automation tool might need to interact with directly &#8212; publish the markdown files that explain how to use it right alongside the tool. That makes it more usable for the end user. If the end user is an LLM, so be it.</p><p>A summary of the tricks:</p><ul><li><p>Build a plugin alongside your project</p></li><li><p>Make an init command to set everything up</p></li><li><p>Make a way to configure the plugin for each project that uses it</p></li><li><p>Create scripts in the plugin - and if they&#8217;ll need to be overridden, create wrapper scripts that can be edited in project-specific ways</p></li></ul><h1>This is good for humans too!</h1><p>The fact that project docs are organised within a Claude plugin does not mean that those docs are not useful for humans. </p><p>Something that&#8217;s quite interesting to me in general is that, in this age of LLM-assisted coding, many software engineering best practices are actually becoming more important, not less. Documentation saves lives. People often neglect documentation, but now it&#8217;s become critically important for harnessing modern tools. </p><p>I&#8217;d argue that this approach is not only good for coding harnesses, but for people too.</p>]]></content:encoded></item></channel></rss>