DEV Community

Charalambos Emmanouilidis
Charalambos Emmanouilidis

Posted on AI-assisted

Spec-Driven Development with AI, Part 1: Making Your Intent Explicit

In my previous post, When Agile Is Not Enough, I wrote about an agent team that produced pull requests faster than I could review them. I could get code written quickly enough. Keeping up with what I was approving was another matter.

Part of that was my own doing. I hadn't written down what I expected the software to do in enough detail. The agent made the missing decisions for me, and I had to find them in the diff. By then, I was reviewing both the implementation and decisions I hadn't even realised needed making.

I'd rather have that conversation before the code exists. Put the intent in a spec, let the agent question it, and agree on what we're building. I still need to review the result, but at least I'll have something to review it against.

That's what I want to try in this series. I'll build a small port scanner for authorized pentests, following it from setup through the first spec to working code. With my background in platform engineering and security, it seemed like a useful project to work through.

Finding a method I could follow

I had already put structure around the agents: architecture decision records to capture decisions, roles to divide responsibilities, and skills and slash commands for recurring tasks. Each helped with part of the problem. Still, the workflow didn't feel complete. I could record individual decisions and tell the agents how to work, but I was still missing a clear, agreed description of what they were supposed to build. I wanted a method that connected those pieces.

I looked at GitHub's Spec Kit and Anatoly Volkhover's book Agentic Spec-Driven Development. Both put specifications at the centre of the work. Spec Kit describes the idea as an inversion: specifications no longer serve the code; the code serves the specifications. There are different approaches under the spec-driven development name, though, so this series follows one particular method, with my own changes along the way.

Spec Kit overwhelmed me at first. There were a lot of folders and commands to get my head around, and I wanted to understand the process as I went. I started with the book.

In Volkhover's workflow, the spec is the source of truth. Code is generated from it and isn't edited by hand. The agent also has a persistent set of rules in the repo, covering how it works and how those rules can change. That's where I started: deciding what the agent is allowed to do, when it should ask, and how I approve a change.

The book provides four bootstrap files to put in an empty repo. Anatoly kindly allowed me to publish my adapted versions in the companion repo, with attribution. I'm not affiliated with him.

Letting the agent use git

One of the first things I wanted to change was the git restriction. The book keeps git away from the AI. I found that strange. If I've reviewed and approved a change, why shouldn't the agent commit it as well?

I wanted it to work on feature branches, make one commit per logical step and use Conventional Commits. Pushing and merging into main would stay with me, along with rebasing and branch cleanup. Git would give me a history I could inspect, without giving the agent permission to do everything git can do.

Normally, I'd open the rules file and add that. In this workflow, I tell the agent what I want and let it analyse the proposed rule first. Here's the shortened request:

RECORD A DURABLE RULE:  
  Add a "Version Control" section to AGENTS.md:  
  - AI MAY create git commits in this repository. AI MUST commit only after  
    a user-approved change is fully recorded, one commit per logical step,  
    using Conventional Commits, with no AI attribution lines.  
  - AI MUST NOT rewrite or destroy history: no push, no force operations,  
    no rebase, no reset, no amend, no branch deletion.  
  - Work happens on feature branches. AI MUST NOT commit directly to main.
Enter fullscreen mode Exit fullscreen mode

The agent reads rule-analysis.md before touching the rules. It works through what my request actually allows and forbids, checks for conflicts with existing rules, checks the wording and resolves any file references.

The rule loop: request, four checks, approval, then write and commit

Its response, cut down:

Allowed: commits. Implied: creating and switching branches.

Forbidden: push, force, rebase, reset, amend, branch deletion, commits on main. Implied: git reset for unstaging, too; use git restore --staged instead.

Not covered: merging into main.

Conflicts: none with existing rules. But nothing stops scratch files in .tmp/ from being committed.

Format: your first bullet bundles several requirements, and the prohibitions name no alternative. Rewritten as twelve rules, one requirement each.

References: only .tmp/, which the Scratch Directory section defines.

I hadn't explicitly ruled out merging into main, even though I meant to keep that for myself. And “no reset” also covered unstaging. This was useful: the agent was making me check the consequences of what I'd asked for before recording it as a rule.

I approved the additions and added another condition: never discard uncommitted work without asking. That had to go through the same analysis. Later, I also required fixup commits for corrections to unmerged commits, so the agent could correct its work without rewriting history.

Where I keep the rules

The bootstrap uses CLAUDE.md. I switched to AGENTS.md because I didn't want the name tied to one tool. Claude Code supports it since version 2.1.277; by default, it loads AGENTS.md when no project CLAUDE.md takes precedence.

I also moved the supporting files into .ai/. The book keeps them at the top level, but I prefer having the agent material together. The specs will go there too. With my glossary added, the setup looks like this:

.  
├── AGENTS.md                      # project intent, rules and file registry  
└── .ai/  
    ├── rule-analysis.md           # checks before a rule change  
    ├── rule-conflict-protocol.md  # procedure for conflicting rules  
    ├── logs/  
    │   └── rule-conflict-log.md   # append-only conflict history  
    └── glossary.md                # project terms and their definitions
Enter fullscreen mode Exit fullscreen mode

AGENTS.md starts with a short description of what the project is for. If I ask for something that doesn't fit, the agent should ask how it relates. It also has instructions for handling undefined terms and conflicting rules. When two rules cannot both be followed, the agent records the conflict and asks me how to proceed.

At the bottom is a file registry: what each file is for and what the agent may do with it. It can edit some files, only read others, and only append to logs. Before creating a file, it has to propose the registry entry and get my approval. It can't quietly invent a new file type or handling protocol either.

That is quite a lot of control over a small repo. It also depends on the agent following the instructions; writing a restriction in Markdown doesn't enforce it. I still have to check its work.

A small detour into souvlaki-case

The glossary gave me a chance to see how the agent handled something it couldn't look up from general knowledge. The familiar foo-bar convention is called kebab-case. I preferred souvlaki-case for this project. Same dashes, different lunch.

The agent stopped and asked what I meant. Fair enough: I'd given it a name, not a definition.

The book's glossary records both a meaning and an anti-meaning. For souvlaki-case, that became lowercase letters and digits joined by dashes; snake_case and camelCase were explicitly excluded. I also added a rule to consult the glossary before asking me about an unclear term. After that, the agent looked it up without asking again.

It's a small example, but that was the behaviour I wanted for project terminology too. Once we'd agreed on a meaning, I wanted it recorded somewhere the agent would consult.

What ends up in the history

Each approved rule change gets its own commit. Here are some from the setup:

chore: bootstrap from agentic-spec.com  
docs(agents): add version control rules  
docs(agents): allow leading-dot names  
docs(agents): require fixup commits for unmerged corrections  
docs(glossary): add project glossary  
docs(agents): consult glossary before asking about terms  
docs(glossary): allow digits in souvlaki-case
Enter fullscreen mode Exit fullscreen mode

If I want to know when the agent got permission to commit, I can find the change and read the conditions. As the specs grow, I want the same traceability for the software: why a feature exists, what we agreed it should do and how that changed. That matters for audits and compliance. It also matters when I return to my own project months later and can't remember what I was thinking.

Would I do this for every repo?

Setting this up took about half a day. The bootstrap is generic, so some of that time went into adapting it for software development, and some went into working out my own rules.

I wouldn't want to repeat all of that for every tool. In an enterprise environment, or if you're going to build several tools this way, it makes sense to put the shared setup in a reusable template. That can include the conventions for your stack, whether you're working in Python, Go or Java. Where there's a platform team, I'd see maintaining that template as part of its job.

Working within my own rules took some getting used to as well. I'd made the process strict enough that even renaming a folder needed analysis and approval. That was partly my own doing. I can still edit .gitignore and git hooks by hand, but at one point I wanted the Stone Age back, when I still wrote code myself.

The more familiar problem was the amount of output. The agent could produce a long rule analysis just as easily as a long diff, and I still had to read it before approving anything. I asked for smaller steps. Moving the conversation into Markdown hadn't given me more time or attention.

I can only approve what I can check, and I can only check small steps.

Next: the scanner spec

The setup gave me a place to record decisions and a process for questioning them before they became rules. The git example had already caught omissions in my instructions. Next, I wanted to see how that would work with the actual scanner requirements.

In Part 2, I add what code generation needs and write the first feature spec. Before the coding agent writes a single line, it comes back with eight questions.

The linked repo snapshot includes later work from the series as well as the setup described here.

Top comments (1)

Collapse
 
charemma profile image
Charalambos Emmanouilidis •

I wrote this piece because I found myself drowning in agent-generated PRs faster than I could manually review them. Letting the AI make undocumented design choices behind the scenes just created a bigger review burden for me later.

I’m really curious; how are you all handling code generation safety and review bottlenecks in your current workflows? Has anyone else experimented with putting specs before the code? Let me know what you think!