dark

How This Blog Got Built by a Swarm of Imps

6 July 2026

Here's the actual sentence I typed:

i want to extend this page to include a simple blog, with @giscus comments enabled

That's it. One line, dropped against a single-page React + TanStack Start site with no markdown pipeline, no comments system, no RSS feed. I didn't write a spec. I didn't sketch a schema. I typed a sentence and watched a swarm of agents turn it into this post.

This is what happened in between.

The plugin

The thing doing the work is imps — a Claude Code plugin I built. Install it and it gives you /imps:imps:

terminal
claude plugin marketplace add seankoji/claude-plugins
claude plugin install imps@seankoji

One line, four modes. Feed it a task description, a batch of GitHub issue numbers, a Discussion URL, or a checklist file, and it figures out what you meant:

InputExampleWhat happens
Task description/imps:imps <task>Refines the brief, plans, decomposes, dispatches, ships a PR
GitHub issue numbers/imps:imps 42 43 51Scouts those issues, dispatches fixes in parallel, opens one integration PR
Discussion URL/imps:imps <discussion url>Seeds the task from a GitHub Discussion, replies there when done
Checklist file/imps:imps checklist.mdRuns every Verify: / Done when: line as an audit

This post came from the first mode. The main session stays deliberately thin — it holds plan approval, the push/PR decision, and nothing else. Everything downstream runs inside a separate subagent so a long build never grinds the orchestrator's own context down.

From a sentence to a brief with teeth

A one-liner isn't a brief. The first thing that happens is translation, not code.

prompt-builder sharpens my sentence into something specific: MDX content, /blog and /blog/$slug routes, Giscus wired with a placeholder config, matching the site's existing look. Then five discovery questions land at once — repo and branch, what artifacts I want out (code, a PR), what counts as done, what's off-limits.

I pick the "higher bar" option on acceptance criteria — RSS feed, SEO meta tags, a scroll-reveal animation — and just like that the scope is bigger than "render some markdown."

One question mid-plan matters more than it looks. The site's root layout sets:

src/routes/__root.tsx
<meta name="robots" content="noindex" />

everywhere, site-wide. Should the blog override that? No. Keep noindex until the site's actually ready for the public. That single answer is why this post carries full Open Graph and Twitter tags — link previews still need to look good — but no search-engine override.

Two agents go looking before anyone plans

Before a plan gets drafted, two read-only Explore agents run in parallel. Neither writes a line of code. Their whole job is telling the truth about the codebase before anyone commits to an approach.

Agent one comes back with the styling contract: TanStack Router's file-based routes, a Reveal scroll-animation wrapper already in use everywhere, a class-based .dark theme with no React context behind it, and a placeholder "writing" section on the homepage that's been quietly waiting for a real blog to link to.

Agent two comes back with the finding that reshapes the whole plan: this isn't plain Vite + Router, it's TanStack Start — a different SSR framework entirely. No MDX pipeline exists. date-fns is installed but nobody imports it. CI runs format → lint → typecheck → build with no test step. And the static file server has no .xml MIME entry — a detail that means nothing yet, but will matter a lot once an RSS feed shows up.

Pipeline: request → brief → discovery → parallel Explore agents → draft plan → Head Imp adversarial review (loops on CHANGES_REQUESTED) → operator approval → Imp Wrangler dispatches 9 tasks → merge → gates → persona panel → draft PR

The shape of one /imps:imps run, start to PR.

The plan gets a bug hunt before I ever see it

The plan comes together as 8 dependency-mapped tasks — a ninth gets folded in after I approve, this post — each one assigned a model by how much judgment it actually needs, not by how big it sounds. Here's the real task table from this run:

tasks.json
{ "id": 1, "label": "Install deps + wire @mdx-js/rollup + ambient types + prettierignore", "model": "sonnet" },
{ "id": 2, "label": "src/lib/blog.ts content loader (getAllPosts/getPostBySlug)",          "model": "sonnet" },
{ "id": 3, "label": "Giscus: GiscusComments.tsx + giscus-config.ts + setup docs",           "model": "sonnet" },
{ "id": 4, "label": "2 sample MDX posts under src/content/blog/",                          "model": "haiku"  },
{ "id": 5, "label": ".blog-prose CSS (semantic tokens only, no hardcoded colors)",         "model": "haiku"  },
{ "id": 6, "label": "Blog routes: layout, index, $slug",                                   "model": "sonnet" },
{ "id": 7, "label": "RSS generation Vite plugin -> /rss.xml + server MIME fix",            "model": "sonnet" },
{ "id": 8, "label": "Homepage integration: real posts, RSS <link>",                        "model": "sonnet" }

Haiku got the two tasks where the shape was already fully specified — filling in sample content and writing CSS against an exact class-name spec. Everything that required actually deciding something — routing, theme sync, a build plugin — went to sonnet.

Before any of it dispatches, an adversarial reviewer reads the plan file itself, not a summary of it, and re-checks every claim against the real repo. This is the Head Imp — running on opus, arguing to kill the plan, not rubber-stamp it. On this run it came back with three real bugs:

Head Imp findings — CHANGES_REQUESTED

  • The RSS feed would download instead of render. The plan said "no app-code changes needed." It was wrong — the server's static-file MIME map has no .xml entry, so rss.xml would ship as application/octet-stream.
  • Prettier — a required CI gate — actually processes .mdx files. The plan reasoned about excluding blog content from ESLint and never once considered Prettier. First blog post committed, CI goes red, for a reason that has nothing to do with the code.
  • The RSS generator would race the build. Planned as a closeBundle hook writing straight to dist/client/rss.xml — except that path can not exist yet when the hook fires, on a site with a multi-environment client+server build.

Every one of those gets fixed in the plan file before I ever see it for approval — plus a handful of minors: unescaped XML in feed titles, a pubDate format that needed to be RFC-822 instead of a plain ISO date, and a task dependency gap that would have let the blog listing get "verified" while it was still empty.

Approve once, then it's out of my hands

I approve the plan a single time. There's no second approval pass after the fixes — they land in the same file before I ever look at it.

From there it hands off entirely to a separate Imp Wrangler, which owns everything downstream: dispatching all 9 tasks as background agents in isolated git worktrees so parallel work can't collide, respecting the dependency graph, merging finished branches, running the gates, and running a persona-panel review before a PR ever opens.

Task dependency graph: task 1 (foundations) fans out to tasks 2, 3, 4, 7, and 9; task 5 is independent; tasks 2, 3, 4, and 5 all feed into task 6 (blog routes); tasks 2 and 7 feed into task 8 (homepage wiring)

The actual dependency graph for this run. Blue = sonnet, purple = haiku.

Task 6 — the blog routes — waits on four upstream tasks before it can start. Everything else with no incoming arrow starts immediately. That's the whole point of mapping dependencies instead of just running things in a list: the wrangler doesn't wait for task 5 to finish before starting task 7, because nothing about the RSS feed depends on the CSS.

This post is task 9 of that 9. By the time you're reading it, the rest of the swarm already finished building the blog around it.

Comments aren't set up yet.