<?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: Grant PS</title>
    <description>The latest articles on DEV Community by Grant PS (@grantps).</description>
    <link>https://dev.to/grantps</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%2F4047885%2Fbf5e0e9a-03bc-49c3-b7ff-d29b167b6108.png</url>
      <title>DEV Community: Grant PS</title>
      <link>https://dev.to/grantps</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/grantps"/>
    <language>en</language>
    <item>
      <title>I gave an AI agent three hours to build my blog</title>
      <dc:creator>Grant PS</dc:creator>
      <pubDate>Mon, 05 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/grantps/i-gave-an-ai-agent-three-hours-to-build-my-blog-17h9</link>
      <guid>https://dev.to/grantps/i-gave-an-ai-agent-three-hours-to-build-my-blog-17h9</guid>
      <description>&lt;p&gt;I gave an AI agent a written plan and a three-hour budget. This is everything it took to get this site live.&lt;/p&gt;

&lt;p&gt;I was about to write another blog post and realised that I can now have my own custom blog with minimal effort. Until now my writing has lived on Medium, in NEXL Engineering, and more recently on dev.to. Both work fine, but I don't have absolute creative freedom over the URL, design or how the article appears on the page.&lt;/p&gt;

&lt;p&gt;So I moved it to my own domain. Rather than pick a platform, I made it an experiment. Can an AI agent build a personal blog in an afternoon, with a publishing workflow I actually like?&lt;/p&gt;

&lt;p&gt;This post is the first one on the blog it describes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the plan first
&lt;/h2&gt;

&lt;p&gt;Before the agent wrote any code, I had Claude interview me with a grill-me skill until we agreed on how the blog should work. The result was &lt;code&gt;PLAN.md&lt;/code&gt;: seventeen settled decisions, each chosen over the alternatives I turned down. That interview is the part of this build anyone can copy, because changing your mind during an interview costs nothing. The plan splits the work into eight phases and lists what the agent is not allowed to do. One phase ends with a checkpoint where the agent has to stop and wait for me.&lt;/p&gt;

&lt;p&gt;The core idea fits in four lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown is the source of truth for every post.&lt;/li&gt;
&lt;li&gt;The agent writes each page's HTML by hand from that markdown, following a design brief. There is no static site generator and no template.&lt;/li&gt;
&lt;li&gt;Plain Ruby scripts build everything that must be exact, which is the RSS feed, the sitemap and a fidelity guard.&lt;/li&gt;
&lt;li&gt;Cloudflare serves the committed files as they are. The deploy has no build step and no API keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second point is the unusual one. Because the agent writes every page by hand, each post can get its own design touches. I can also redesign the whole site later by having the agent regenerate every page from the markdown, since no theme ties the pages together.&lt;/p&gt;

&lt;p&gt;It also means I have to trust an agent with my words, which is why the guard exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  A guard for my words
&lt;/h2&gt;

&lt;p&gt;The plan tells the agent never to alter, add to, reorder or omit my prose. I have written before about how often agents forget instructions like that, so I didn't rely on it.&lt;/p&gt;

&lt;p&gt;The fidelity guard renders the markdown, pulls the post body out of the agent's HTML, and compares the two block by block. It checks headings, paragraphs, list items and every line of code. Whitespace may change and nothing else may. If a curly quote turns straight or a code line goes missing, the guard fails and prints a diff that shows the agent what to fix.&lt;/p&gt;

&lt;p&gt;The guard runs in a pre-commit hook, so neither the agent nor I can commit a page that changes my words. In the end-to-end test, it blocked a commit where one word on the page differed from the markdown.&lt;/p&gt;

&lt;p&gt;Later the home page intro got the same treatment. A redesign rewrites the home page, so the intro now has its own markdown file and the guard checks it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three designs, then a fourth
&lt;/h2&gt;

&lt;p&gt;The checkpoint was the design. The agent built three directions, each with a home page and a sample post. One was an editorial serif, one was an engineering notebook, and one was loud, in cobalt, coral and lemon.&lt;/p&gt;

&lt;p&gt;I asked for a fourth that combined the notebook with the loud one, then picked the original notebook anyway. I had two conditions, mobile first and proper web accessibility. The agent rewrote the stylesheet mobile first and worked through the accessibility details one at a time. The blinking cursor now stops after four blinks, and screen readers skip the decorative shell prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the guard could not see
&lt;/h2&gt;

&lt;p&gt;This was the most useful lesson of the build.&lt;/p&gt;

&lt;p&gt;The guard proves the words are right, but it can't tell whether the page reads well. Screenshots at phone and desktop width found eight bugs that no text check would catch. Four of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paragraphs had no gap between them, because one CSS rule outranked another on specificity.&lt;/li&gt;
&lt;li&gt;My name overflowed the screen on a phone.&lt;/li&gt;
&lt;li&gt;The font had no glyph for a non-breaking hyphen.&lt;/li&gt;
&lt;li&gt;Code ligatures merged &lt;code&gt;#{&lt;/code&gt; into a single glyph, so the code on screen didn't match the code in the post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the agent wrote a second checker, &lt;code&gt;bin/check-pages&lt;/code&gt;. It runs axe-core accessibility checks in light and dark mode, checks for sideways scrolling at three widths, and saves screenshots that the agent has to look at before it hands me a page. The plan never asked for it, and I'd now keep it over almost anything else in the repo.&lt;/p&gt;

&lt;p&gt;In its build log the agent wrote that it should have built the screenshot checker in phase 2 instead of phase 5. I agree. I made the same point in &lt;a href="https://dev.to/grantps/tooling-every-ai-software-harness-should-have-4512"&gt;my tooling post&lt;/a&gt;, that a tool only helps if the agent reads its output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;The build took 2 hours 35 minutes of wall time, including the time the agent spent waiting for me to pick a design. That left 25 minutes of the budget. At the end the repo had 42 passing tests and a pre-commit hook proven against every failure case in the plan. It also had two Claude Code skills, &lt;code&gt;/publish&lt;/code&gt; and &lt;code&gt;/regenerate-site&lt;/code&gt;, and a finished site that nobody could see yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting it live
&lt;/h2&gt;

&lt;p&gt;I decided to set up Cloudflare myself, since it was simple enough to do by hand. The agent walked me through it and checked each step from its side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I think more people will build their own
&lt;/h2&gt;

&lt;p&gt;One thought kept coming back while I did this.&lt;/p&gt;

&lt;p&gt;I could have used Medium, Ghost, WordPress, or a static site generator with a theme. Any of them would have worked, and each would have made me accept someone else's idea of what a blog is.&lt;/p&gt;

&lt;p&gt;What I have instead matches how I want to work. I write in markdown, and a guard I can read checks every page against it. The agent can redesign every page without touching a post, and the deploy just copies files. No product will add that combination to its roadmap, because I'm probably the only person who wants exactly this.&lt;/p&gt;

&lt;p&gt;Building your own used to cost weeks of evenings, and next to that a monthly subscription looked cheap. Now it costs an afternoon of agent time and a plan worth following, and I think that changes the answer for a lot of people.&lt;/p&gt;

&lt;p&gt;I expect more and more people to build their own software instead of buying off the shelf. I don't think that holds for everything. When many people share the same problem and mistakes are expensive, a product built and maintained by a team will still win. For the tools closest to how you work every day, I think building your own will become the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this costs
&lt;/h2&gt;

&lt;p&gt;Building your own has real costs, and I don't want to skip them.&lt;/p&gt;

&lt;p&gt;I own all of it. If something breaks or needs a security fix, no vendor will do it for me. I kept that small on purpose, since the site is static files with no server code and no database. A bigger system would cost a lot more to look after.&lt;/p&gt;

&lt;p&gt;The plan did a lot of the work. The agent moved fast because I had already made the decisions. Without the plan, I would have made those same decisions halfway through the build, when each one costs more to change.&lt;/p&gt;

&lt;p&gt;I also had to build my trust in the site myself. The guard and the page checks were a large part of the engineering, and without them I'd be reviewing every page by eye.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leaves things
&lt;/h2&gt;

&lt;p&gt;The site is live at grant-ps.com, and the source is public at &lt;a href="https://github.com/grantspeelman/grant-ps.com" rel="noopener noreferrer"&gt;github.com/grantspeelman/grant-ps.com&lt;/a&gt;. Every post links to the markdown it was generated from. Next, dev.to will import posts from the RSS feed and point back to this site as the original. After that I want to build an editor app, and that will be the second post.&lt;/p&gt;

&lt;p&gt;It's a wonderful feeling to be able to dream up something and see it come to life within a few hours. If you've put off building your own version of something because an off-the-shelf one was close enough, it might be worth another look. Happy coding.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blogging</category>
      <category>cloudflare</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Tooling every AI software harness should have</title>
      <dc:creator>Grant PS</dc:creator>
      <pubDate>Sat, 22 Aug 2026 16:08:43 +0000</pubDate>
      <link>https://dev.to/grantps/tooling-every-ai-software-harness-should-have-4512</link>
      <guid>https://dev.to/grantps/tooling-every-ai-software-harness-should-have-4512</guid>
      <description>&lt;p&gt;When an agent writes the code, you stop reading every line. You can pretend otherwise for a while, but on any real codebase you end up skimming a 600 line diff and approving it because nothing jumped out. Nothing jumping out is not the same as nothing being wrong.&lt;/p&gt;

&lt;p&gt;Documentation and a well tended CLAUDE.md only take you so far. The longer an agent runs, the more those instructions get crowded out of context and quietly forgotten. Instructions are advisory. Tooling is not.&lt;/p&gt;

&lt;p&gt;Two ideas run through everything below.&lt;/p&gt;

&lt;p&gt;The first is that the economics of enforcement have changed. Every quality threshold we set as an industry was really a judgement about how much human time it was worth spending. 100% branch coverage was not wrong, it was expensive, so we called it diminishing returns and settled at 80%. That cost is now paid in agent time, which is cheap and which you are not spending your evening on. Thresholds that were uneconomic five years ago are now just settings. Turn them up.&lt;/p&gt;

&lt;p&gt;The second is that a tool only counts if the agent sees the output. A linter that fails in CI thirty minutes after the agent stopped is a message to you. A linter the agent runs itself, reads, and fixes before you ever see the branch is part of the harness. Same tool, completely different value. Everything on this list should be reachable by the agent with one command, and should fail loudly enough that it cannot be ignored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;The obvious one, but push it further than you would for a human team. Have a testing framework, and set minimum line and branch coverage to 100%.&lt;/p&gt;

&lt;p&gt;That number is doing something specific. At 80%, an agent will happily leave the awkward branch untested, because the awkward branch is exactly the one that is annoying to set up. At 100% there is no judgement call left to get wrong, and no negotiation to have.&lt;/p&gt;

&lt;p&gt;On an older codebase this is not feasible in one go. What does work is applying the 100% minimum only to files touched in a PR. Coverage ratchets up as the agent works through the code, and you get the high standard on new work immediately.&lt;/p&gt;

&lt;p&gt;I use: &lt;a href="https://rspec.info/" rel="noopener noreferrer"&gt;https://rspec.info/&lt;/a&gt; and &lt;a href="https://github.com/simplecov-ruby/simplecov" rel="noopener noreferrer"&gt;https://github.com/simplecov-ruby/simplecov&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutation testing
&lt;/h2&gt;

&lt;p&gt;Agents love writing code. Left alone they add checks that look defensive and do nothing. An empty check before looping over an array is the classic: looping over an empty array already does nothing, at the same speed, so the check is pure noise that a reviewer now has to read and reason about.&lt;/p&gt;

&lt;p&gt;Coverage will not catch this. The line is executed by your tests, so it counts as covered. Mutation testing will, because it changes that line and finds no test cares. It is the only tool I know that answers both halves of the question at once: is every concept actually covered by a test, and does every piece of code actually add value.&lt;/p&gt;

&lt;p&gt;Have a look at &lt;a href="https://stryker-mutator.io/docs/" rel="noopener noreferrer"&gt;https://stryker-mutator.io/docs/&lt;/a&gt; for the basics.&lt;/p&gt;

&lt;p&gt;I use: &lt;a href="https://github.com/mbj/mutant" rel="noopener noreferrer"&gt;https://github.com/mbj/mutant&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Type checking
&lt;/h2&gt;

&lt;p&gt;A quick win, and useful to agents in a way it is not to humans. An agent generating code from a half remembered API will produce a method call that looks entirely plausible and does not exist. Type checking closes that gap statically, before anything runs, and gives the agent a precise error to act on rather than a stack trace it has to interpret.&lt;/p&gt;

&lt;p&gt;Even dynamic languages have a route in now. TypeScript for JavaScript, Sorbet for Ruby.&lt;/p&gt;

&lt;p&gt;I use: &lt;a href="https://sorbet.org/" rel="noopener noreferrer"&gt;https://sorbet.org/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Linters and custom linters
&lt;/h2&gt;

&lt;p&gt;Every mature language has tooling that flags patterns that are outdated or actively harmful. This matters more with agents than it did with people, because an agent's instinct comes from its training data, and its training data is full of code written years ago. It will reach for the idiom that was correct in 2019 with total confidence.&lt;/p&gt;

&lt;p&gt;Rubocop catches exactly this. In a Rails codebase it will flag &lt;code&gt;Time.now&lt;/code&gt; where you want &lt;code&gt;Time.current&lt;/code&gt;, the timezone safe equivalent. That is not a style preference, it is a bug, and it is the sort of bug an agent will reintroduce every few weeks unless something stops it.&lt;/p&gt;

&lt;p&gt;There are linters for CSS, HTML, Go, JavaScript and most other things. If the language is mature, one exists.&lt;/p&gt;

&lt;p&gt;I use: &lt;a href="https://github.com/rubocop/rubocop" rel="noopener noreferrer"&gt;https://github.com/rubocop/rubocop&lt;/a&gt;, &lt;a href="https://github.com/Shopify/erb_lint" rel="noopener noreferrer"&gt;https://github.com/Shopify/erb_lint&lt;/a&gt;, &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;https://eslint.org/&lt;/a&gt;, &lt;a href="https://github.com/remarkjs/remark-validate-links" rel="noopener noreferrer"&gt;https://github.com/remarkjs/remark-validate-links&lt;/a&gt;, &lt;a href="https://github.com/hadolint/hadolint" rel="noopener noreferrer"&gt;https://github.com/hadolint/hadolint&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security checkers
&lt;/h2&gt;

&lt;p&gt;Agents write insecure code. The saving grace is that they are insecure in well documented ways, because they learned it from existing codebases, and those mistakes already have detectors built for them. String interpolation into a query, a secret pasted into a config file, an over permissive workflow permission: all findable automatically.&lt;/p&gt;

&lt;p&gt;Non negotiable on any codebase where agents write a meaningful share of the code.&lt;/p&gt;

&lt;p&gt;I use: &lt;a href="https://brakemanscanner.org/" rel="noopener noreferrer"&gt;https://brakemanscanner.org/&lt;/a&gt;, &lt;a href="https://github.com/gitleaks/gitleaks" rel="noopener noreferrer"&gt;https://github.com/gitleaks/gitleaks&lt;/a&gt;, &lt;a href="https://github.com/zizmorcore/zizmor/" rel="noopener noreferrer"&gt;https://github.com/zizmorcore/zizmor/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Duplication detection
&lt;/h2&gt;

&lt;p&gt;Agents take copy paste to the next level, and this is one of the few problems that gets harder specifically because you did not write the code yourself. When you duplicate a block, you know you did it, and you feel the itch. When an agent does it in a file you have not opened, there is nothing to notice. The diff looks like new code, because to you it is.&lt;/p&gt;

&lt;p&gt;A detector gives you the thing your memory would have given you if you had been typing.&lt;/p&gt;

&lt;p&gt;I use: &lt;a href="https://jscpd.dev/" rel="noopener noreferrer"&gt;https://jscpd.dev/&lt;/a&gt; and &lt;a href="https://github.com/seattlerb/flay" rel="noopener noreferrer"&gt;https://github.com/seattlerb/flay&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hooks
&lt;/h2&gt;

&lt;p&gt;This is where the list turns into a harness.&lt;/p&gt;

&lt;p&gt;Every tool above is worth ten times more when the agent runs it, sees the failure, and fixes it, versus when it fails in CI and waits for you. Hooks are the cheapest way to force that, because they sit at points the agent cannot route around.&lt;/p&gt;

&lt;p&gt;The part worth taking advantage of is that there is more than one such point. An agent's work has natural stages, editing a file, committing, pushing, and you can attach different checks to each. Feedback is cheapest at the earliest stage where the check can meaningfully run, because the further the agent gets from the code it wrote, the more context it has to rebuild to fix it. So push each check as early as it will go, and let cost decide how much further it slides.&lt;/p&gt;

&lt;p&gt;Post edit is the tightest loop available. If your type checker is fast, run it here rather than waiting for the commit: the agent gets the error while the file it just wrote is still the thing it is thinking about, which is a much cheaper fix than discovering it three files later. Same for linters, if they are fast or can be pointed at a single file. Immediate feedback on one file beats a list of twenty violations at the end.&lt;/p&gt;

&lt;p&gt;Pre-commit is for anything that needs the whole change rather than one file, or is too slow to run on every edit. The full lint pass, the formatter, secret scanning, the fast part of the test suite.&lt;/p&gt;

&lt;p&gt;Pre-push is the last cheap stop before CI. Full tests, duplication detection, the slower security scanners. Getting a failure here still costs the agent a minute. Getting it from CI costs a round trip and usually costs you as well.&lt;/p&gt;

&lt;p&gt;Set up right, the agent commits, the hook fails, the agent reads the output and tries again, and you find out about none of it. Green CI first time, or at the very least the common problems caught before a human is involved.&lt;/p&gt;

&lt;p&gt;If you take one thing from this article, take this section.&lt;/p&gt;

&lt;p&gt;I use: &lt;a href="https://lefthook.dev/" rel="noopener noreferrer"&gt;https://lefthook.dev/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code formatters
&lt;/h2&gt;

&lt;p&gt;Worth having for the usual reason, that all code should look like one person wrote it. There is a second reason with agents: formatting noise is the enemy of skim reading, and skim reading is what you are now doing. Every gratuitous whitespace change is a line of diff competing for attention with a line that actually changed behaviour.&lt;/p&gt;

&lt;p&gt;Cheapest item on the list too, since most formatters auto correct without the agent needing to do anything.&lt;/p&gt;

&lt;p&gt;I use: &lt;a href="https://github.com/rubocop/rubocop" rel="noopener noreferrer"&gt;https://github.com/rubocop/rubocop&lt;/a&gt;, &lt;a href="https://prettier.io" rel="noopener noreferrer"&gt;https://prettier.io&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Just in time documentation injection
&lt;/h2&gt;

&lt;p&gt;Context is the scarce resource. Filling it with only what is needed is often the difference between an agent that acts smart and one that acts stupid, and the usual failure is not too little documentation but too much, loaded up front, crowding out the thing that mattered.&lt;/p&gt;

&lt;p&gt;So having a way to inject context only at the point the agent needs it is worth building. I could not find one that worked the way I wanted, so I wrote agent-apropos, which does just in time context injection across Claude Code, OpenCode, Gemini CLI and Copilot CLI.&lt;/p&gt;

&lt;p&gt;I use: &lt;a href="https://github.com/NEXL-LTS/agent-apropos" rel="noopener noreferrer"&gt;https://github.com/NEXL-LTS/agent-apropos&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom checkers
&lt;/h2&gt;

&lt;p&gt;If you have a practice that is non standard, or nuanced enough that no off the shelf tool encodes it, write your own. Most linters let you extend them with custom rules, and writing a standalone checker has never been easier, not least because you can get an agent to write it.&lt;/p&gt;

&lt;p&gt;This is also the highest leverage item here over time. The off the shelf tools encode what everyone knows. Custom checkers encode what your team knows, which is the part an agent has no other way of learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this costs
&lt;/h2&gt;

&lt;p&gt;Worth being straight about the trade offs, because they are real.&lt;/p&gt;

&lt;p&gt;Mutation testing is slow. It is not something you run on every commit on a large suite, and you will need to think about where in the workflow it fits.&lt;/p&gt;

&lt;p&gt;100% coverage can buy you test shaped noise if you are not careful, tests written to satisfy the threshold rather than to describe behaviour. Mutation testing is the counterweight, which is part of why those two belong together.&lt;/p&gt;

&lt;p&gt;Hooks that take too long will get bypassed, by you as much as by the agent. Keep the pre-commit set fast and push the slow things later.&lt;/p&gt;

&lt;p&gt;None of that changes the conclusion. It just means picking the order. If you are starting from nothing: hooks and a formatter first, because they cost almost nothing and make everything after them enforceable. Then coverage, then security. Mutation testing when you can afford it.&lt;/p&gt;

&lt;p&gt;Hopefully you, or your agent, found this useful.&lt;/p&gt;

</description>
      <category>agenticdev</category>
      <category>ruby</category>
      <category>software</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
