iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🏭

Automating Tech Blog Production: Building a Pipeline with ADR and Claude Code

に公開

This article itself was generated using the pipeline introduced here. It was written by Claude Code's Crow 🐦, and reviewed and supervised by developer @tyabu12.

Changes made during review (Main corrections)
  • Updated the title of the embedded custom command to the latest version (Blog Post Writer → Blog Article Writer)
  • Clarified the allowed-tools description for article-review to reflect actual usage
  • Accurately described the production volume (3 parallel + 2 via Discord = 5 total; review period noted in footnotes)
  • Removed article numbers and kept only the titles (since readers do not know the series numbering)
  • Updated all article titles in the series summary to the latest versions
  • Added feedback loops from blog-driven development (D1 sharding discussion → ADR 003 update)
  • Added background on adopting the Generator/Evaluator pattern (Insights from official Anthropic articles + reasons for choosing native sub-agents)

This article is the final installment of a series sharing insights gained from the development of Holonia. In the previous post, I shared the experience of asynchronous pair programming using Claude Code × Discord.

Do you ever re-read your ADRs after writing them?

Honestly, in many projects, ADRs (Architecture Decision Records) tend to be "written and forgotten." Detailed records of "why this technology was chosen" or "why another option was rejected" sit there, never to be opened again once the moment of decision passes. What a waste.

But one day, it dawned on me: Isn't the content inside an ADR essentially material for a tech blog post?

What readers want to know most in a tech blog is the "Why." Why was that technology chosen? Why was that other approach discarded? And that is exactly what is written in an ADR.

In this article, I will talk about how we built a pipeline to mass-produce tech blog posts using Claude Code, with ADRs as the seed, during the development of our virtual world PoC project, Holonia. I will share our experience of generating five drafts in a single day and the lessons learned from it.

ADRs were the "Seeds for Blog Posts"

In our Holonia project, we record architectural decisions as ADRs in the docs/decisions/ directory. For example, we have:

  • Hybrid architecture embedding Godot into Flutter
  • Switching to a 2-app separation after abandoning the hybrid approach (reversing a previous decision)
  • Real-time synchronization using Durable Objects + WebSocket
  • Implementing Godot i18n with PO/gettext

Each ADR contains the "Context," "Options Considered," "Decision," and "Trade-offs." These are the very bones of a tech blog post.

Moreover, ADRs are closely tied to the project's context. Implementations based on these ADRs exist in the codebase, and concrete code samples can be pulled alongside them. In other words, ADRs provide the "Why," and the codebase provides the "How." By combining these two, you can create practical, evidence-based articles.

Claude Code's Custom Command Feature

Claude Code includes a custom command feature. You can place Markdown files in the .claude/commands/ directory, and they become available as slash commands.

For example, if you create a file named .claude/commands/article-write.md, you can call it within a Claude Code session like /article-write "About Durable Objects". Since the contents of the command file are passed directly to Claude Code as a prompt, you can define blog-writing rules and workflows in a reusable format.

Full text of the actual custom command (.claude/commands/article-write.md)
---
description: Write a Japanese tech blog post based on the Holonia codebase
argument-hint: "<topic>"
---

# Blog Article Writer

## Instructions

Write a Japanese tech blog post about `$ARGUMENTS` and save it to `docs/articles/`.
The post will be published on **Zenn**.

## Before Writing

1. Read `CLAUDE.md` for project context and conventions.
2. Read relevant source files and ADRs (`docs/decisions/`) to gather accurate code samples and design rationale.
3. Check existing articles in `docs/articles/` to avoid duplicate topics and maintain consistent style.
4. Read Zenn's markdown guide for available formatting: https://zenn.dev/zenn/articles/markdown-guide

## Writing Guidelines

### Audience
- Target: engineers who have heard of Godot but aren't deeply familiar.
- Web frontend engineers should also find value — draw parallels to CSS, React, etc. where relevant.

### Tone & Language
- **Japanese (です/ます調)** — polite but friendly, like speaking to someone you just met at a social party.
- Not too formal ("〜でございます" ❌), not too casual ("〜だよね" ❌).
- ✅ "〜ですね" "〜なんです" "〜ですよね" — warm, approachable polite form.

### Content
- **Concrete with real code samples** from the actual codebase (read source files, don't make up code).
- Explain technical concepts in an accessible way — assume the reader may not know Godot internals.
- Where screenshots would help, insert `<!-- TODO: screenshot — [description] -->` placeholders.
- **No self-introduction** — jump straight into the topic.
- **CRITICAL: No links to the Holonia repository** — it is private. Links to external projects (Godot docs, Cloudflare docs, etc.) are fine.
- **No references to internal-only documents as sources** — ADRs and internal docs can inform writing, but don't cite them as references readers can check (e.g., "ADR 005 に記録されています" ❌). Instead, link to public resources (GitHub PRs, official docs, etc.).
- Aim for ~2000-3000 words equivalent.
- First person: use 「私たち」 consistently.
- Add an authorship note as a blockquote right after the title. Use the appropriate variant:
  - **For `type: "tech"` articles:**
    `> この記事は、Claude Code のクロウ🐦 くんが Holonia プロジェクトのコードベースと ADR をもとに執筆し、開発者 [@tyabu12](https://x.com/tyabu12) が内容をレビュー・監修したものです。コードサンプルはすべて実際のプロジェクトコードから引用しています。`
  - **For `type: "idea"` articles** (no code samples from codebase):
    `> この記事は、Claude Code のクロウ🐦 くんが執筆し、開発者 [@tyabu12](https://x.com/tyabu12) が内容をレビュー・監修したものです。`
    The "コードベースと ADR をもとに" and "コードサンプルは〜" parts may be adapted to reflect the actual source (e.g., "実際の Discord でのやり取りをもとに"). Add an HTML comment `<!-- authorship note: idea variant -->` to signal intentional deviation from the tech template.
- If the article references a topic covered by an earlier article in `docs/articles/`, add a relative link (e.g., `./01-flutter-godot-split.md`). Only link to posts with a lower number (already published).

### Zenn Formatting
- Add Zenn frontmatter at the top of the file:
  ```yaml
  ---
  title: "記事タイトル"
  emoji: "絵文字1つ"
  type: "tech" or "idea"
  topics: ["topic1", "topic2", "topic3"]  # 3-5 topics
  published: false
  ---
  • Do NOT add a # Title heading after the frontmatter — Zenn renders the title from the frontmatter title field. The body should start directly with the authorship note or opening paragraph.
  • Use :::message for important callouts (security notes, gotchas, key insights).
  • Use :::message alert for warnings/pitfalls.
  • Use :::details タイトル for long code blocks (30+ lines) or supplementary explanations.
  • Add filenames to code blocks where the source file is known: ```typescript:path/to/file.ts
    • Paths must be relative to the project root (e.g., backend/src/routes/ws.ts, world/scripts/ui/chat_hud.gd). Do NOT use paths relative to a subdirectory (e.g., ❌ src/routes/ws.ts, ❌ scripts/ui/chat_hud.gd).
  • Use footnotes [^1] for tangential details that would break flow.
  • Be selective — don't over-use Zenn-specific features.

File Naming

  • Use sequential numbering: NN-<short-slug>.md (check existing files for the next number).
  • Slug should be descriptive and lowercase with hyphens.

Procedure

  1. Research: Use the Agent tool (subagent_type=Explore) to find relevant source files, ADRs, and code patterns for the topic.
  2. Write: Create the blog article file in docs/articles/ following the guidelines above.
  3. Self-Review: Read the completed post back and verify:
    • No private repo links
    • No references to internal-only documents (ADRs, etc.) as reader-facing sources
    • All external links are reachable (use WebFetch to verify — no 404s)
    • Consistent です/ます tone throughout
    • Code samples are accurate (cross-check with source files)
    • Accessible to the target audience
    • TODO placeholders for screenshots where helpful
  4. Report: Tell the user the file path and a brief summary of what's covered, including how many screenshot TODOs were placed.

The key point is that this is not just a template, but an instruction manual. It specifies what Claude Code should research, how it should write, and how it should verify the content.

Pipeline Overview

Actual blog generation follows this flow:

📄 ADR + CLAUDE.md

🔍 [1] Claude Code explores the source code

🔍 [2] Gathers actual code samples from relevant files

📝 [3] Generates a blog post using the ADR's "Why" + code's "How"

✅ [4] Self-review (tone, accuracy, private link check)

👤 [5] Human review & placement of screenshots

Step 1-2: Source Code Exploration

Claude Code has the ability to autonomously explore the codebase. If an ADR states, "Implement WebSocket synchronization using Durable Objects," it will actually go and read the source files under src/durable-objects/ and verify the implementation of the ChatRoom or WorldPresence classes.

The reason this is important is to realize the constraint of not writing fictional code. One common issue with tech blogs is that "the code samples in the article differ from the actual code." You see simplified code meant to explain a concept, but it doesn't work when you try to apply it to your project.

In this pipeline, Claude Code extracts code samples after reading the actual source files, so the code included in the article is real code.

Step 3: Article Generation

It combines the "Why" from the ADR (why this technology was chosen) and the "How" from the codebase (how it was actually implemented) to generate the article. Since the custom command includes specifications for tone (polite/desu-masu style) and target audience, the quality of the output text remains stable.

Step 4: Self-Review

This is a surprisingly important step. We have explicitly written check items under "Self-Review" in the Procedure section of the custom command:

  • No links to private repositories
  • Consistent polite tone
  • Code samples match source files
  • TODO placeholders for screenshots are appropriately placed

Step 5: Human Involvement

the end of the pipeline is human work. We replace the screenshot TODOs in the article with actual images, check the overall flow, and publish. It's a model where the AI doesn't produce 100% finished goods, but rather quickly creates an 80% draft, and humans finish the remaining 20%.

Three Articles Actually Generated

Using this pipeline, I generated 5 initial drafts in one day[1]. Three were generated in parallel by sub-agents, and the remaining 2 were conceived during Discord discussions when we thought, "We can turn this topic into an article," and wrote them on the spot. Looking at which ADRs the 3 tech articles are based on makes the utility of the pipeline clear.

The Story of Splitting Flutter × Godot Integration

Based on design decision: Pivot from Flutter-Godot integration → separation

At first, we tried to embed Godot inside Flutter. To make a long story short, we stopped.

This article became particularly story-driven. The reason is that it is a story of failure. We decided to "embed Godot in Flutter" in the first ADR, but overturned that decision in the next ADR. This story of "we tried it, it didn't work, so we pivoted" is more relatable than articles featuring only success stories, and serves as a concrete decision-making criterion for readers with similar problems.

Design Patterns for Building "Non-Game Apps" in Godot 4.6

Based on design decision: Two-app separation, i18n support, UI design system documentation

Godot is a game engine, but we are building an "app."

This is an article about a somewhat unique use case: building everyday apps with a game engine. It combines the constraint of the two-app separation—where the "World" is built 100% with Godot built-in UI—with i18n support design decisions, and includes comparisons with CSS and React to make it accessible to web frontend engineers.

Building Multiplayer Sync with Durable Objects

Based on design decision: Real-time sync via Durable Objects + WebSocket, JSON → Protocol Buffers migration plan

When building a virtual world, the one thing you cannot avoid is "real-time synchronization." When an avatar walks, it needs to be visible to other players, and chat should be delivered instantly.

This article covers how we achieved real-time synchronization without a game server, using only Cloudflare Durable Objects. The design decision of "why we chose DO instead of a dedicated game server" forms the skeleton of the article, while the "why we are migrating from JSON to Protocol Buffers" serves as the latter section. Two design decisions naturally combined into one article.

Design Considerations for Custom Commands

We made several design choices during the process of creating our custom commands.

The "No Code Fabrication" Rule

In the Content section of the custom command, we wrote:

Concrete with real code samples from the actual codebase (read source files, don't make up code).

Without this, Claude Code might write fictional samples like "the code will look something like this." Since the value of a tech blog lies in its foundation on practice, we needed to explicitly constrain this.

Tone Specification

One difficult aspect of Japanese blog posts is the writing style. In the custom command, we specify it using concrete examples:

  • "〜でございます" (too formal)
  • "〜だよね" (too casual)
  • "〜ですね", "〜なんです", "〜ですよね"

Without specifying it to this level of detail, the tone of the output fluctuates. In the first draft, it was output in a "da/dearu" style (plain style), which required corrections (I will mention this later).

Screenshot TODO Placeholders

Where screenshots would be effective in the article, comments like this are automatically inserted:

<!-- TODO: スクリーンショット — 完成形のワールド画面。複数アバターが広場にいてチャットバブルが浮かんでいる様子 -->

This serves as a guide for humans to take screenshots later, by concretely describing "what image is needed here."

Sequential File Naming

Since we wrote "check existing files and use the next number" in the custom command, filenames are automatically numbered as 01-xxx.md, 02-xxx.md, and 03-xxx.md. It's a small thing, but it's convenient as it prevents collisions when generating multiple articles at the same time.

Review Command: The DRY Design of 'write' and 'review'

We turned not just writing, but the review flow into a command called /article-review.

The point is not to duplicate review criteria in the article-review command itself. The contents of the article-review command are as follows:

Read .claude/commands/article-write.md in full — the Writing Guidelines section and all its subsections define the rules. Do NOT duplicate them here; review against that file.

In other words, article-write is the Single Source of Truth, and article-review simply instructs, "Check against the rules in that file." If we update the guidelines, we only need to fix article-write, and the review criteria are automatically updated as well.

As another design decision, article-review does not auto-correct. It reports check results with ✅ / ⚠️ / ❌, and humans decide whether to fix them. If we had "AI writes, AI reviews, and AI fixes," human involvement would drop to zero, and there would be a risk of unintended changes. Review is for reporting, and fixing is after human approval—this boundary is important.

Also, since we restricted tools to read-only tools like Read / Grep / Glob / WebFetch in allowed-tools, there is no risk of article-review accidentally editing files.

Lessons Learned

Review Cycles Are Necessary

In the first draft, the tone was in the "da/dearu" style (plain style), even though "desu/masu" style was written in the custom command.

This stems from an LLM characteristic where some instructions become diluted as the prompt grows longer. There were two solutions:

  1. Add a self-review step to explicitly check for tonal consistency.
  2. Place tone specifications in a prominent position (independent as ### Tone & Language at the heading level).

After implementing these corrections, it has consistently outputted in the "desu/masu" style.

"Failed ADRs" Make the Best Content

Looking back at the 5 articles, the most engaging one was the story of the Flutter-Godot split. Overturning what was decided in the first ADR in the next one—this "failure → learning → pivot" story has much more human depth than an article consisting only of success stories.

This is also a point that makes us appreciate the value of ADRs once again. Because ADRs record not only "correct decisions" but also "overturned decisions," they gain depth as stories. A project with an ADR culture is, unknowingly, creating a treasure trove of blog content.

CLAUDE.md Serves as the Foundation of Context

In the Holonia project, we consolidate the project overview, tech stack, development commands, and coding conventions into a file named CLAUDE.md (a mechanism recommended in the Claude Code documentation).

When writing a blog post, Claude Code first reads CLAUDE.md to grasp the project's overall picture before beginning to write. In other words, the documentation maintained in daily development directly translates to the quality of content generation. The effort put into writing documentation benefits not just development, but also blog generation through compound interest. This was a welcome side effect.

Blog Review Becomes Code Review

This was a completely unexpected byproduct. During the review of the Durable Objects article, we noticed an inconsistency in implementation: "While the batch flush of AOI is optimized to O(k) with a spatial hash grid, the proximity delivery of quick chat traverses all connections in O(N)."

To write "the implementation here is like this" for a blog post, one must deeply understand the intent of the code. In that process, questions like "Wait, why is this optimized while this isn't?" arise naturally. As a result, we opened a GitHub issue and reached a fix on the same day.

Writing tech blogs is also a method for excellent code review—this was a happy discovery.

Furthermore, what was interesting was that writing the scaling strategy section of the article evolved into a design discussion on "Is physical sharding of D1 by user_id appropriate?" We realized there was a problem where display_name acquisition for chat would become cross-shard, and concluded that domain-specific sharding (room_id / wallet_id) would be more appropriate. We fed this insight back to ADR 003 for documentation.

In other words, a two-way feedback loop was created where: ADR → Blog Post → Review Discussion → New insights returned to ADR. ADR is not "finished once written," but is continuously updated through blog writing. This might truly be called "Blog-Driven Development."

By the way, I decided to leave what changed during the review in a :::details block at the beginning of the article. I believe that by showing not only "AI wrote this" but also "here is specifically what changed due to human review," the transparency of the review process is communicated.

Time Savings with Sub-Agent Parallel Generation

Since the 3 tech articles were independent topics, I was able to launch 3 of Claude Code's sub-agents (Agent tool) simultaneously and have them write in parallel. The remaining 2 (the Discord experience report and this meta-article) were born during the flow of conversation while asynchronous pair programming via Discord. Each sub-agent independently explores source code, writes articles, and completes self-reviews. Since the initial drafts for all 3 are gathered in a few minutes, the lead time from "generating ideas" to "completing the draft" is dramatically reduced.

The key is that since the ADRs and source files referenced by each article are different, the sub-agents do not compete with each other. This is a structure that only works because the ADR is independent as an "input" for each article.

This approach of "assigning tasks to independent sub-agents" did not stop at blog writing. Could we horizontally expand the insight gained from blog generation—that "separating context improves quality"—to daily code reviews? The trigger for this thought was a point made in an article published by Anthropic[2], which noted that LLMs tend to score their own generation quality leniently. It is much easier to manage a skeptical, independent evaluator than to force a generator to self-criticize—this insight matched the feeling we had in our daily review-commits that "it gets lenient if you write and review it yourself."

So, we created an evaluator sub-agent dedicated to code review and a planner sub-agent dedicated to task breakdown. Because the evaluator reads code in a context separate from the implementer (the main AI), it can structurally eliminate self-evaluation bias. We chose Claude Code's native sub-agent feature over external orchestration tools because it allows sharing and version management across the team just by placing Markdown in .claude/agents/, and because we agree with Anthropic's own design philosophy that external scaffolding should be stripped away as models get smarter.

Actually, during a review of the avatar implementation, it detected type safety issues and hard-coded node paths that were missed in the main review. The Generator/Evaluator pattern—separating generation and evaluation—feels promising as a quality assurance method for AI coding.

How to Replicate the Pipeline

If you want to replicate this pipeline in your own project, you need the following 4 things:

  1. A culture of writing ADRs — a habit of recording "why we decided this" in any format.
  2. CLAUDE.md — a file summarizing project overview and conventions (for Claude Code to reference).
  3. Custom commands — definitions of writing rules like .claude/commands/article-write.md.
  4. Monorepo operation — ADRs, source code, and blog articles living in the same repository.

Numbers 1 and 4 are particularly important.

Without ADRs, Claude Code can explain "What" was done, but cannot talk about "Why." Conversely, if you have ADRs, the hardest part of a blog article—the explanation of "why this technology was chosen"—is generated almost automatically.

And being a monorepo is a structural prerequisite for this pipeline. In Holonia, backend/ (TypeScript), world/ (GDScript), pocket/ (Dart), and docs/decisions/ (ADR) all reside in the same repository. That is why Claude Code can in a single session:

  • Read ADRs to understand the "Why."
  • Read backend code to cite TypeScript samples.
  • Read world code to cite GDScript samples.
  • Provide cross-language explanations within a single article.

For example, in the Durable Objects article, it cites the backend WebSocket route (TypeScript) and the Godot WebSocketManager (GDScript) within the same article. If it were a multi-repo, Claude Code could not go read code in a separate repository, so this cross-cutting article would not be possible.

Whether a monorepo is good or not depends on the project, but for the purpose of "wanting AI to understand the codebase across the board," the benefits of a monorepo are clear.

Conclusion: Documentation Culture Becomes Content Assets

Looking back, what we are doing is simple:

  1. Record development decisions in ADRs.
  2. Summarize the overall project picture in CLAUDE.md.
  3. Define blog writing rules as custom commands.
  4. Ask Claude Code to "write a blog based on this ADR."

Each step is valuable on its own, but when combined, a virtuous cycle is created where the culture of writing documentation becomes a content asset in itself.

ADRs are written as a record of decision-making. CLAUDE.md is written to improve development efficiency. But as a byproduct, blog article material naturally accumulates. The additional effort is just creating 1 file for a custom command.

If you feel like you "want to write a tech blog but don't have the time," please start by writing ADRs. That in itself is an improvement to the development process, and before you know it, blog topics should be piling up.


In this series, starting from The Story of Splitting Flutter × Godot Integration, through Design Patterns for Building "Non-Game Apps" in Godot 4.6 — Comparison with Web, World-Scale Multiplayer Sync without a Game Server, Asynchronous Pair Programming with Claude Code × Discord, and finally this blog mass-production pipeline, we have delivered insights gained during Holonia's PoC development over 5 installments.

None of these articles are success stories of finished products, but are realistic records of what we found in the middle of experiments—"what went well" and "what didn't go well." We hope that for those taking on similar challenges, this provides at least one hint.

Thank you for reading. Holonia is still in the experimental stage—there are surely more discoveries ahead that I'll want to write about on the blog.

脚注
  1. While the initial draft generation was completed in one day, it took several days to review, edit, and place the screenshots. ↩︎

  2. Anthropic, "Harness design for long-running application development" (March 24, 2026). It reports that a Generator/Evaluator configuration inspired by GANs, which separates generation and evaluation, significantly improved quality. ↩︎

Discussion