Companion repo for the article Text-First: If It Can Be Text, It Should Be Text. Start on the
bare-repobranch and compare your result withimplementation.
This repo serves as an example of my "Text-First" approach to AI-assisted software development.
When planning to implement any non-trivial feature with AI, I typically follow a simple process:
%% Pin every color to the Amber Mono palette so GitHub's light/dark theme defaults don't leak in.
%%{init: {"theme": "base", "themeVariables": {
"darkMode": true,
"background": "#0d0a08",
"primaryColor": "#1a1410",
"primaryTextColor": "#ffb000",
"primaryBorderColor": "#ffb000",
"textColor": "#ffb000",
"nodeTextColor": "#ffb000",
"lineColor": "#c80",
"arrowheadColor": "#c80",
"clusterBkg": "#0d0a08",
"clusterBorder": "#c80",
"titleColor": "#fc0",
"edgeLabelBackground": "#0d0a08"
}}}%%
%%{init: {"flowchart": {"subGraphTitleMargin": {"top": 12, "bottom": 24}}}}%%
graph LR
subgraph before["Planning & Design"]
direction TB
discuss["1 Discuss"]:::node-strong --> design["2 Design"]:::node-strong --> plan["3 Plan"]:::node-strong
end
subgraph code["Coding & Maintenance"]
direction TB
implement["4 Implement"]:::node-soft --> test["5 Test"]:::node-soft
test --> refactor["6 Refactor"]:::node-soft
test -->|"Error"| implement
end
before --> code
%% Declared forward so the layout keeps Planning on the left; linkStyle then
%% drops the forward arrowhead. 6 = this link's position (0-based).
before <-.->|"next iteration"| code
linkStyle 6 marker-end:none
classDef early fill:#1a1410,color:#ffb000,stroke:#ffb000;
classDef late fill:#231c16,color:#ffb000,stroke:#ffb000;
class discuss,design,plan early;
class implement,test,refactor late;
The first three steps are the "Text-First" part. They are all about deriving an implementation plan. I start by discussing and designing the feature with AI which yields documentation artefacts that serve a dual purpose: they're the blueprint for implementation, and they become the reference for future maintenance and evolution - consumed by humans and AI alike.
Once we have a clear plan (most tools offer a "Plan mode"), we can hand off to implementation ("Agent mode") and let AI do the heavy lifting of writing code, tests and documentation. Modern LLMs are reasonably good at verifying their own work, especially with clear test specifications. Therefore testing is typically a mixture of the AI auto-correcting its own mistakes and adding manual tests that ensure correctness and also serve as living documentation at the same time.
The final refactoring step is something often overlooked - regardless of AI assistance - but it's crucial to keep the codebase clean and maintainable in the long run - current LLMs are not great at grasping the big picture and often introduce redundancies and inconsistencies that need to be ironed out by the developer.
Hold on, you might say, that's yet another Spec-Driven Development (SDD) approach, isn't it? Not quite. It shares the spec-first approach and many of its principles, but I refuse to introduce yet another framework that forces me into abstraction lock-in.
After two decades in the industry, I've seen too many frameworks come and go. What happens when your SDD framework of choice ceases to exist? You're left with a pile of specs that just lost their meaning.
No, Text-First is a non-framework framework. It is simply a set of principles and best practices to bring order and reproducibility to the chaos of creating and maintaining software with AI.
Text-First: If it can be text, it should be text.
Lightweight, focused on the essentials, easy to extend and most importantly: not tied to a specific tool or framework. It works with any LLM, can be used with GitHub Copilot, Claude Code, Codex, Cursor, Antigravity, or whatever comes next. No tooling circus. No hocus-pocus. Just text.
If SDD is the GitFlow of AI-assisted software development, then Text-First is its GitHub Flow.
Whatever you feed an LLM - PDF, Word, Excel, Visio diagrams or voice transcripts - it ends up as text tokens anyway. So why pay the conversion tax instead of feeding it text in the first place?
Even as context windows grow, token cost remains a concern. Among human-readable formats, Markdown has the highest information density per token you can get - which makes Text-First the most token-efficient approach to spec-driven development available.
The goal of this repo is to implement a simple ToDo REST API using the Text-First approach. The branches are meant to be read as stages of that process:
maintells the story and explains the methodology.bare-repois the initial state before the AI-assisted implementation starts.implementationcontains one sample final state after running through the process.
The implementation is in Java using Spring Boot 4, and the bare-repo branch contains the initial
empty project that was derived from the Spring Initializr with the following
dependencies:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
implementation 'org.springframework.boot:spring-boot-starter-validation'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}You need Java 25. The project uses the Gradle Wrapper, so no separate Gradle installation is required.
Run the tests:
./gradlew testStart the application on http://localhost:8080:
./gradlew bootRunOn the bare-repo branch, this starts the initial Spring Boot application before the ToDo API has been
implemented. On the implementation branch, the same commands run the completed sample API and its tests.
To avoid having to restate the same information over and over again in prompts, an AGENTS.md describing the project context, conventions and architecture serves as the single source of truth for all implementation details. It is the "spec" that guides the implementation and maintenance of the API.
These documents contain the authoritative API contract and the ToDo resource model. They are the blueprints
for implementation and must be read and followed before making any non-trivial changes to the API. They also serve as living documentation that can be referred to by both humans and AI in the future. Similar to
the README.md, they start as placeholders in the bare-repo branch and are filled with actual content as the implementation progresses. More specifically, they are the output artifacts of the "Design" step in the Text-First process, and they are the reference for the "Plan" and "Implement" steps that follow.
Creating and modifying API endpoints is governed by a workflow described in the
create-endpoint skill, which ensures that all changes are
properly documented and follow the established conventions.
Spring Boot 4 is not well-represented in current LLM training data, so the
create-test skill teaches the model how to write
Spring Boot 4 tests by explaining the package layout and testing conventions. This usually results in one-shot
success instead of trial-and-error.
Both Codex and GitHub Copilot support skills in the .agents/skills directory, but Claude still expects them in
the .claude/skills directory. To avoid duplication, we keep all skills in the .agents/skills directory and simply
symlink it to .claude/skills.
On the bare-repo branch, the README.md is a stub with placeholder content that will be replaced with the
actual documentation as the implementation progresses. This is a contract documented in the AGENTS.md that
ensures that the README is always up-to-date and serves as a reliable source of information about the project.
On the main branch, the README.md contains the narrative overview you are currently reading π.
On the implementation branch, it should read like the README of a completed ToDo API project, including
the generated endpoint overview and usage details.
Since we did most of the heavy lifting in AGENTS.md and the create-endpoint and create-test skills,
the prompts guiding the actual implementation are surprisingly simple and concise.
To simulate a typical "Discuss" step, we start with a simple prompt that just asks what a typical
ToDo REST API should look like:
What endpoints should a simple ToDo API provide?
The result will already be guided by the conventions and architecture described in AGENTS.md and the create-endpoint and create-test skills. Think of these prompt customization files as the "guardrails" that keep the implementation on track and ensure that the output is consistent with the established conventions and architecture.
We deliberately stay in the same chat session to follow the "Generated Knowledge" prompting pattern, which allows the LLM to build on the previous output and maintain context throughout the implementation process.
Entering the "Design" step, we now ask the LLM to create a Mermaid class diagram based on the API endpoints
that it just proposed for the ToDo resource:
Propose a Mermaid class diagram for a simple ToDo resource served by the simple ToDo API.
Based on the non-deterministic output of the LLM, the proposed attributes of the ToDo resource will typically vary, and we'll have to iterate to achieve exactly the set of attributes we want. Iterate until the resulting class diagram consists of a ToDo class with the following attributes:
- id: Long
- title: String
- description: String
- completed: Boolean
You can use prompts such as the following to guide the iteration:
Leave `createdAt` and `updatedAt` out for now.
Once everything looks good, we tell the agent to persist what we just designed in our stub documentation artifacts:
- Add the Mermaid diagram to `doc/todo-class-diagram.md`.
- Add the API endpoints you proposed above to `doc/api-endpoints.md`.
To preserve the conversation so far, its rationale, insights and design decisions, a quick and dirty way is to save the conversation dump in a simple Markdown file, such as doc/api-discussion.md. If you'd like a more structured approach, you can extract key points and decisions into separate Architecture Decision Records (ADRs).
We have everything we need and can now enter the "Plan" step and let the agent figure out how to implement the API based on the documentation we just created. If your AI tool supports a "Plan mode", you may want to switch to it. The prompt for this step is as simple as:
Plan the initial implementation of the ToDo API.
Since the plan itself is a valuable piece of documentation that can be referred to in the future, we can either ask the agent
to persist it or simply copy and paste it ourselves, e.g. into a file such as doc/implementation-plan.md. This way, we have
a record of the implementation process that can be referred to in the future, both by humans and AI.
Finally, we can enter the "Implement" step and let the agent do the heavy lifting of writing code, tests and
documentation based on the plan we just created. Most AI tools offer a button to kick off implementation; otherwise,
just prompt the agent to start implementing:
Start implementing the API based on the implementation plan you just created.
or just:
Start implementation.
Since we made tests a mandatory part of the implementation process in the AGENTS.md and the create-test skill, the
resulting implementation will typically include tests that are not only ensuring correctness but also serve as living
documentation for the API. Additionally, the LLM will use the tests it generates to verify its own work and auto-correct
any mistakes it makes along the way, which results in a much smoother implementation process. No more copying stacktraces
back and forth between the IDE and the LLM, no more manual debugging.
Once the implementation is done, the LLM will typically tell you how to run the application and the tests, or you may have
a look at the README.md which should have been updated with the relevant information as part of the implementation process.
The final "Refactor" step is something that often gets overlooked in SDD frameworks (and elsewhere π), but it's crucial
to keep the codebase clean and maintainable in the long run. AI can assist with it. Just prompt it to refactor the
codebase based on the established conventions and architecture, and it will typically do a decent job at ironing out
any inconsistencies and redundancies that may have been introduced during implementation. Example prompts:
Refactor the codebase to ensure consistency with
the established conventions and architecture.
or
Identify duplicate or almost identical code and refactor it to honor the DRY principle.
or simply
Do you see any potential for refactoring?
Please explain your reasoning and list them in order of importance.
For a more detailed write-up, have a look at the article in my blog and/or follow klautcode@Mastodon, klautcode@Bluesky or emboss@X for updates.
Released under the MIT License β feel free to copy, adapt, and reuse anything in this repo (including the Text-First approach itself) in your own projects.