Showing posts with label Augmented Coding. Show all posts
Showing posts with label Augmented Coding. Show all posts

Sunday, March 01, 2026

Encoding Experience into AI Skills

I'd been tweaking my augmented coding setup for months - adjusting CLAUDE.md rules, adding instructions for testing discipline, complexity management, incremental delivery. Things I've repeated to every team I've worked with, now repeated to AI agents. It worked, but it felt like writing the same email over and over.

Then I found Lada Kesseler's skill-factory.


What Skills Are (And Why They Matter)

If you use Claude Code, you already know about CLAUDE.md - a file where you put instructions that the agent reads at the start of every conversation. It works. But it has a problem: everything is always loaded. Your TDD guidelines, your Docker best practices, your refactoring workflow - all of it competing for the agent's limited context window, whether it's relevant or not.

Skills solve this differently. They're packaged knowledge that activates only when relevant. You type /mutation-testing and the agent gains deep expertise about finding weak tests through mutation analysis. You type /complexity-review and it becomes a technical reviewer that challenges your proposals against 30 dimensions of complexity. The rest of the time, that knowledge stays out of the way.

Think of it as progressive disclosure for AI context. The agent gets what it needs, when it needs it.

The Discovery: Lada Kesseler's Skill Factory

Lada Kesseler built the skill-factory - a repository with 315 commits of carefully crafted skills covering serious engineering ground: TDD, Nullables (James Shore's pattern for testing without mocks), approval tests, refactoring (using Llewellyn Falco's approach), hexagonal architecture, event modeling, collaborative design, and more.

These aren't toy prompts. The Nullables skill alone includes reference material for infrastructure wrappers, embedded stubs, output tracking, and three different architectural patterns. The approval-tests skill covers Java, Python, and Node.js with scrubbers, reporters, and inline patterns. This is deep, carefully structured knowledge.

Lada also co-created augmented-coding-patterns - a catalog of 43 patterns, 14 obstacles, and 9 anti-patterns for working effectively with AI coding tools. It's a collaboration between Lada Kesseler, Ivett Ordog, and Nitsan Avni. If you're doing augmented coding and haven't seen it, stop reading this and go look.

What I found wasn't just a collection of skills. It was an approach to sharing engineering knowledge with AI agents that I hadn't seen anywhere else.

The Fork as Extension

The natural next step wasn't to start from scratch - it was to fork and extend. Lada's skills already covered testing fundamentals, design patterns, and AI-specific workflows. What I noticed missing were the practices I kept explaining repeatedly: how to manage complexity, how to deliver incrementally, how to make sure tests actually catch bugs.

So I added 11 skills. Not because 16 wasn't enough, but because my particular set of problems needed particular solutions.

You can find my extended fork at github.com/eferro/skill-factory with all 27 skills ready to use.

Testing rigor

test-desiderata - Kent Beck's 12 properties that make tests valuable. Not "does this test pass?" but "is this test isolated? composable? predictive? inspiring?" I was tired of AI generating tests that had coverage but no diagnostic power. This skill makes the agent evaluate tests against each property and suggest concrete improvements.

mutation-testing - The question code coverage can't answer: "Would my tests catch this bug?" Coverage tells you what your tests execute. Mutation testing tells you what they'd detect. I'd already written a blog post about this - now it's a reusable skill. The examples are in Python and JavaScript, but I'm also using it successfully with Go.

Delivering incrementally and managing complexity

This is where the skills chain together, and where things get interesting.

story-splitting - Detects linguistic red flags in requirements ("and", "or", "manage", "handle", "including") and applies splitting heuristics. It's the first pass: is this story actually three stories wearing a trenchcoat?

hamburger-method - When a story doesn't have obvious split points but still feels too big, this skill applies Gojko Adzic's Hamburger Method: slice the feature into layers, generate 4-5 implementation options per layer, then compose the thinnest possible vertical slices.

small-safe-steps - The implementation planner. Takes any piece of work and breaks it into 1-3 hour increments using the expand-contract pattern for migrations, schema changes, API changes. Core belief: risk grows faster than the size of the change.

complexity-review - My inner skeptic, encoded. Reviews technical proposals against 30 dimensions of complexity across 6 categories (data volume, interaction frequency, consistency requirements, resilience, team topology, operational burden). Pushes for the simplest viable approach. Use it when someone says "Kafka" and you want to ask "why not a queue?"

code-simplifier - Reduces complexity in existing code without changing behavior. The cleanup crew after a feature is done.

These five skills work as a pipeline: story-splitting -> hamburger-method -> small-safe-steps for delivery planning, with complexity-review as a gate before implementation and code-simplifier as a sweep after.

Practical tools and team workflows

thinkies - Kent Beck's creative thinking habits, turned into a skill. When you're stuck, it applies patterns like "What would I do if I had infinite resources?", "What's the opposite of my current approach?", "What would make this problem trivial?" It's less about code and more about unsticking your thinking.

traductor-bilingue - Technical translation between English and Spanish that keeps terms like "deploy", "pull request", "pipeline", and "staging" in English (because that's how Spanish-speaking dev teams actually talk). Small thing, but it saves constant corrections.

dockerfile-review - Reviews Dockerfiles for build performance, image size, and security issues.

modern-cli-design - Principles for building scalable CLIs: object-command architecture (noun-verb), LLM-optimized help text, JSON output, concurrency patterns.

A Skill in Action

To make this concrete, here's what the delivery planning pipeline looks like in practice.

Say you have a story: "As a user, I want to manage my notification preferences including email, SMS, and push notifications with scheduling and quiet hours."

Step 1 - You invoke /story-splitting. The agent immediately flags "manage", "including", and the conjunction "and" joining three notification types plus scheduling. It suggests splitting into at least 4 stories: one per notification channel plus quiet hours as a separate slice.

Step 2 - You take the first slice ("email notification preferences") and invoke /hamburger-method. It breaks the feature into layers (UI, API, business logic, persistence) and generates options for each. For the UI layer: (a) full settings page, (b) single toggle, (c) link to email with confirmation, (d) inline in profile. It composes the thinnest vertical slice: a single toggle with an API endpoint and a database flag.

Step 3 - You invoke /small-safe-steps on that thin slice. It produces a sequence of 1-3 hour steps: add the database column with a migration, add the API endpoint with tests, add the UI toggle, wire it together. Each step deployable independently.

No single skill does everything. They compose. That's the point.

How to Get Started

If you want to try these:

  1. Fork the repo: github.com/eferro/skill-factory (my extended fork with 11 additional skills for complexity management and incremental delivery) or the original by Lada Kesseler
  2. Install skills: The repo includes a skills CLI tool. Run ./skills toggle to browse and select which skills to install into your Claude Code setup.
  3. Use them: Type /skill-name in Claude Code. /mutation-testing to check your tests. /complexity-review to challenge a design. /small-safe-steps to plan your next implementation.
  4. Make your own: The repo includes documentation and tooling for creating new skills. Fork it, add what you need, share it back.

Standing on Shoulders

The total is 329 commits, 27 skills across 6 categories. But the number that matters most is that Lada built 315 of those commits. I added 14. The original structure, the skill manager, the testing and design skills that form the foundation - that's all her work. What I did was extend it with the practices I personally find myself repeating.

This is how open source has always worked: someone builds something good, others extend it, and the whole thing becomes more useful than any individual could make it. With AI skills, the effect compounds differently - every skill that gets shared becomes available to every person using it, making good practices almost free.

Lada's augmented-coding-patterns site (with Ivett Ordog and Nitsan Avni) takes this even further - it's not just tooling but a shared vocabulary for how we work with AI. Skills, patterns, obstacles, anti-patterns: a growing body of community knowledge.

What knowledge do you find yourself repeating to your AI agents? What practices would you encode as skills?

The barrier to sharing isn't technical anymore. It's deciding to do it.

References

Sunday, February 22, 2026

Podcast: AI as an Amplifier. Why Engineering Practices Matter More Than Ever

Vasco Duarte invited me to be part of the Scrum Master Toolbox Podcast's AI Assisted Coding series, and I couldn't pass up the chance to talk about something I've been living and thinking about intensely for the past several months.

The conversation builds directly on the experiment I documented in Fast Feedback, Fast Features: My AI Development Experiment: 424 commits over 11 weeks, where for every unit of effort I put into new features, I invested four times more in refactoring, cleanup, tests, and simplification. And yet, globally, I think I more or less doubled my pace of work.

In the episode, we dig into several things I've been exploring:

Vibe coding vs production AI development. Both are valid—but they require different mindsets. Vibe coding is flow-driven, exploration-focused, great for prototypes and discovery. Production AI coding demands architectural thinking, security analysis, and sustainability practices. Even vibe coding benefits from engineering discipline as soon as experiments grow beyond a weekend hack.

The positive spiral of code removal. One of the most powerful patterns I've discovered is using AI to accelerate deletion. Connect product analytics to identify unused features, use AI to remove them efficiently, and you trigger a cycle: simpler code makes architecture changes cheaper, cheaper architecture changes enable faster feature delivery, which creates more opportunities for simplification. Humans historically avoided this because removal was as expensive as creation. That excuse is gone.

Preparing the system before introducing change. Rather than asking "implement this feature," I've been asking "how should I change my system to make this feature trivial to introduce?" AI makes that preparation cheap enough to do routinely. The result: systems that evolve cleanly rather than accumulating debt with each addition.

AI as an amplifier—the double-edged sword. This is the central idea. AI doesn't replace engineering judgment; it magnifies its presence or absence. Strong teams will see accelerated improvement. Teams without good practices will generate technical debt faster than ever. The path to excellence in modern software development lies in the seamless integration of a high-performance engineering culture, lean-agile product strategies, and an evolutionary approach to architecture. AI makes that path wider—but you still have to choose to walk it.

🎙️ Listen to the episode: AI as an Amplifier—Why Engineering Practices Matter More Than Ever

Sunday, January 18, 2026

Fast Feedback, Fast Features: My AI Development Experiment

What happens when you use AI not to ship faster, but to build better? I tracked 424 commits over 11 weeks to find out.

The Experiment

Context first: I'm an engineering manager, not a full-time developer. These 424 commits happened in the time I could carve out between meetings, planning, and leadership work. The applications are production internal systems (monitoring dashboards, inventory management, CLI tools, chatbot backends) used by real teams, but not high-criticality systems where a bug directly impacts external customers or revenue.

Important nuance: I also act as Product Manager for the Platform team that owns these applications. This means I'm defining the problems and implementing the solutions. There's no friction or information loss between problem definition and implementation that typically exists in stream-aligned teams where PM and developers are separate roles. This setup favors faster iteration and tighter feedback loops (though it's worth noting this isn't representative of how most teams operate).

From November 2025 to January 2026, I wrote 424 commits across 6 repositories, spanning 44 active days (with Christmas holidays in the middle). Every single line of code was written with AI assistance: Cursor, Claude Code, the works. These weren't toy projects or weekend experiments. These were real systems evolving under active use.

The repositories varied wildly in maturity: from a 13-day-old Go service to a 5.6-year-old Python system with over 12,000 commits in its history. Half were greenfield projects under 6 months old; half were mature codebases years into their lifecycle. Combined, they represent ~107,000 lines of production code. These are small-to-medium projects. That's how our platform team works: we prefer composable systems over monoliths.

The period was intense: 9.6 commits per day average, almost double my historical pace. But AI didn't just make me faster at writing code. It fundamentally changed what kind of code I wrote.

I tracked everything. Every commit was categorized using a combination of commit message analysis, file change patterns, and manual review. Claude Sonnet 4.5 helped automate the initial categorization, which I then validated. And when I analyzed the data, I found something I wasn't expecting.

The Balance

For every hour I spent on new features, I spent over four hours on tests, documentation, refactoring, security improvements, and cleanup.

22.7% functionality. 98.3% sustainability.

Yes, that adds up to more than 100%. That's not an error: it's the reality of how development actually works. When I develop a feature, the same commit often includes tests, documentation updates, and code cleanup. The numbers reflect that commits are multidimensional, not mutually exclusive categories.

The ratio: 0.23:1 (Functionality:Sustainability)

This wasn't accidental. This was a deliberate experiment in sustainable velocity. And AI made it possible.

Breaking Down the 98.3%

8-Dimensional Commit Categorization

When I say "sustainability," I mean 8 specific, measurable categories:

  • Tests: 30.7%: The largest single category
  • Documentation: 19.0%: READMEs, API docs, inline comments
  • Cleanup: 13.8%: Removing dead code, unused features, simplification
  • Infrastructure: 12.0%: CI/CD, scripts, tooling improvements
  • Refactoring: 11.5%: Structural improvements, better abstractions
  • Configuration: 8.1%: Environment variables, settings, build configs
  • Security: 3.2%: Vulnerability fixes, security audits, input validation

These aren't "nice-to-haves." They're the foundation that makes the 22.7% of new functionality actually sustainable.

What Changed (And What Didn't)

Here's what I learned: tests and feedback loops were always important. Good engineers always knew this. The barrier wasn't understanding, it was economics and time.

What was true before AI:

  • Fast feedback loops were critical for velocity
  • Comprehensive tests enabled confident iteration
  • Documentation reduced knowledge silos
  • Some teams invested in this, many didn't grasp that sustainable software requires sustained investment in technical practices

What changed with AI:

  • The barrier to entry dropped dramatically
  • Building that feedback infrastructure became fast
  • Maintaining quality became economically viable for small teams
  • The excuse of "not enough time" largely disappeared

What didn't change:

  • Discipline is still our responsibility
  • The choice to balance features vs sustainability is still ours
  • AI doesn't automatically make us write tests: we have to choose to
  • The default behavior is still "ship more features faster" until technical debt forces a halt

The insight: AI removed the last excuse. Now it's about discipline, not capability.

For me, as a manager who codes in limited time, this changed everything. I can afford to build the feedback infrastructure that lets me iterate fast. The 0.23 ratio isn't a constraint, it's what enables the velocity I'm experiencing.

Negative Code: Simplification as a Feature

Here's another data point: 55,407 lines deleted out of 135,485 total lines changed.

That's 40.9% deletions. For every 3 lines I wrote, I deleted 2.

Some deletions were refactoring: replacing 100 lines of messy code with 20 clean ones. But many were something else: removing features that didn't provide enough value.

One repository, chatcommands, has net negative growth: the codebase got smaller despite active development. It's not alone. ctool also shrank during this period.

This connects to two concepts I've written about before:

Basal Cost of Software: Every line of code has an inherent maintenance cost. It needs to be understood, tested, debugged, and updated. The best way to reduce basal cost is to have less code.

Radical Detachment: Software is a liability to minimize, not an asset to maximize. The goal isn't more code, it's the right amount of code to solve the problem.

Before AI, deleting features was expensive:

  • Understanding old code took hours (documentation outdated)
  • Tracing dependencies was manual and error-prone
  • Verifying nothing broke required incomplete test suites
  • Updating docs and configs was tedious

Features became immortal. Once added, they never left, even at zero usage.

With AI, deletion becomes viable:

  • Trace dependencies in minutes, not hours
  • Comprehensive tests catch breaking changes immediately
  • Documentation updates happen alongside code changes
  • The entire deletion commit includes proper cleanup

The 13.8% cleanup category isn't just removing dead imports. It's removing dead features. Entire endpoints. Unused UI components. Configuration options nobody sets.

I call this Negative Velocity: making the codebase smaller, simpler, and faster, not just adding more.

This aligns with lean thinking about waste elimination. Every unused feature is waste: it increases build times, slows down tests, complicates mental models, and raises the basal cost of the system. Each line of code creates drag on everything else. By deleting features, we're not just cleaning up: we're reducing the ongoing cost of ownership. Fewer features means faster comprehension, simpler debugging, easier onboarding, and less surface area for bugs.

I'd deleted code before, but AI reduced the friction enough to make it routine instead of occasional. Deletion went from expensive to viable. We can finally afford to minimize the liability at the pace it deserves.

The best code is no code. Now we can actually afford to delete it.

The Metrics at a Glance

The key numbers:

  • 424 total commits across 44 active days (November 2025 - January 2026)
  • 9.6 commits per day average: nearly double typical velocity
  • Ratio Func:Sust = 0.23:1 (1 hour features, >4 hours sustainability)
  • Average Functionality: 22.7% per commit
  • Average Sustainability: 98.3% per commit (multidimensional, not mutually exclusive)
  • 135,485 total lines changed (80,078 insertions, 55,407 deletions)
  • 40.9% deletion ratio: for every 3 lines written, 2 deleted

These aren't aspirational numbers. These are the actual patterns from an intensive 11-week period of AI-assisted development in production repositories.

Different Projects, Different Profiles

Not every project should have the same ratio. Context matters.

  • inventory: 0.42:1 ratio: More feature-focused, greenfield project in active development
  • plt-mon: 0.25:1 ratio: Test-heavy, mature monitoring system needing reliability
  • ctool-cli: 0.16:1 ratio: CLI tool with emphasis on tests and robustness
  • chatcommands: 0.15:1 ratio: Maintenance-focused, net negative code growth (-1,809 lines)
  • ctool: 0.09:1 ratio: Minimal feature work, heavy focus on infrastructure and cleanup
  • cagent: 0.13:1 ratio: New project with emphasis on quality from day one

The chatcommands profile is particularly interesting: 31.5% of effort went to cleanup, and the repository actually shrank by 1,809 lines over this period. This isn't a dying project, it's a maturing one. Features were removed intentionally because they weren't providing value. The codebase got simpler, faster, and more maintainable.

The plt-mon repository maintains a 1.15:1 test-to-feature ratio: tests slightly outpace features. This is a production monitoring system where reliability matters, and the balance reflects steady feature growth with corresponding test coverage.

The ratio should reflect the project's phase and needs. AI makes all of these profiles viable without sacrificing quality or velocity.

What I Learned

After 11 weeks and 424 commits, here's what I've discovered:

Real velocity comes from fast feedback loops. Not from writing code faster, but from being able to iterate confidently and quickly. The 98.3% investment in sustainability isn't overhead, it's what enables speed.

AI changed what became economically viable. Before, building comprehensive test coverage as a manager with limited coding time would have been impossible. Now I can afford to build both the features and the safety net at sustainable pace. The barrier dropped; the discipline remains my responsibility.

Speed ≠ Velocity. Speed is how fast you move. Velocity is speed in the right direction. A team shipping 10 features per week with zero tests is moving fast toward a rewrite. A team shipping 3 features per week with comprehensive test coverage is moving fast toward sustainability.

What you optimize for gets amplified. My hypothesis: AI amplifies our choices. If you optimize for feature velocity, you'll accumulate technical debt faster. If you optimize for sustainable velocity (balancing features with quality infrastructure) you'll build healthier systems faster. I've seen this play out in my own work, though I don't claim this is universal.

Deletion is a feature. With lower barriers to understanding and changing code, we can finally afford to make codebases smaller. Net negative growth isn't stagnation, it's maturity.

The right ratio depends on context. My 0.23:1 ratio works for internal systems with moderate criticality, developed by a manager in limited time. Your context is different. The point isn't to copy my numbers, it's to be intentional about the balance.

This is still an experiment. I don't know if this approach scales to all teams or all types of systems. What I do know: for my context, over these 11 weeks, this balance produced the fastest sustainable velocity I've experienced in my career.

The shift wasn't learning new practices—I'd practiced TDD and built for sustainability for years. But as a manager coding in limited time, I always had to compromise. I wrote tests, but not as many as I wanted. I refactored, but not as thoroughly. I documented, but not as completely. AI didn't change what I valued—it changed what I could afford to do. The discipline I'd always practiced could finally match the standard I'd always wanted.

Your Turn

I don't have universal answers. But I do have a suggestion:

Measure your balance. Be intentional about it.

Track your next month of commits. Categorize them honestly. Calculate your Functionality:Sustainability ratio.

The number itself matters less than the awareness. Are you making conscious choices about where AI velocity goes? Are you building the feedback infrastructure that enables sustainable speed? Are you just shipping faster, or are you building better systems faster?

For me, the answer has been clear: investing heavily in tests, documentation, and simplification has made me faster, not slower. The 98.3% isn't overhead, it's the engine.

Your mileage may vary. Your context is different. But the question is worth asking:

What kind of engineering does AI make viable for you that wasn't before?

Related Posts

Saturday, December 20, 2025

Radical Detachment in the AI Era: Reinventing How We Build Software

Let me start with a disclaimer. I'm an enthusiast who loves trying new things, what some might call an early adopter, a geek, and perhaps a little bit obsessed. And right now, I'm enjoying this moment in our industry immensely, like a pig in mud.

A few months ago, something shifted. I had my "wow" moment, a realization that this changes everything.

For the last couple of years, as a Head of Engineering managing teams of 25 to 37 people, I had become one of those managers who "used to code." My chances of writing anything substantial were practically zero. In all of 2024, I made maybe 40 contributions to my personal repositories: a few commits here and there, mostly small fixes or experiments that went nowhere. The kind of sporadic activity you see from someone who loves coding but has accepted the inevitable trade-off of leadership.

Then, in early 2025, something happened. While still managing the same large team, with the same responsibilities and time constraints, I suddenly found myself shipping code at a pace I hadn't seen since I was an individual contributor. Over 800 contributions in just a few months. Not toy projects. Real, ambitious systems I'd kept on a mental backlog labeled "someday when I have time." The tools that made this possible? Primarily Cursor and Claude Code, with some experimentation with Codex along the way.

That's when it hit me: "This changes everything. Nothing will be the same again." We are now entering terra incognita, unknown territory. It's a time of total uncertainty, but it's also a time of unprecedented opportunity, which is what I want to explore with you.

To Build the Future, We Must First Invent It

"The best way to predict the future is to invent it." That was the philosophy of Alan Kay and his team at Xerox PARC in the 1960s and 70s. They didn't think about building products; they focused on inventing the future itself.

Their approach was grounded in a set of powerful principles. They thought about fundamental problems, not products, aiming to solve deep human or professional challenges. They identified exponential trends and created a 30-year vision. For them, it was Moore's Law, which they used to imagine what would be possible decades later. They explored with radical freedom in intensely interdisciplinary teams, mixing computer scientists with physicists, philosophers, and ethicists, believing groundbreaking ideas came from these intersections. They followed a "demonstrate or die" principle where ideas were nothing without practical, working prototypes. They built tools to build other tools, layering their inventions to create a platform for future innovation. And they always used systemic thinking, focusing on the whole rather than just optimizing individual parts.

A perfect example is the Dynabook. In 1968, they created a cardboard prototype of a portable personal computer. They knew the hardware didn't exist yet, but they trusted Moore's Law to make it a reality within 30 years. From this long-term, visionary thinking came some of the most foundational technologies of our time: Smalltalk, laser printers, Ethernet, graphical user interfaces (GUIs), and the personal computer. They also took object-orientation, evolved it, and carried it to its extreme. Steve Jobs later famously took inspiration from their work and brought many of these ideas to the masses.

A 30-Year Glimpse into Our AI-Powered Future

Following the Xerox PARC model, let's identify the current exponential trends in AI. The capability of LLMs is constantly increasing, with more parameters and larger context windows. The cost of inference is falling exponentially. And if the trends I'm observing hold, the speed of code generation could increase 10x every 3-4 years, though this is more intuition based on current trajectories than hard data.

These trends force us to ask some provocative questions about our profession. Will the strong distinction between "programmer" and "non-programmer" continue to exist? What will our role be when 99% of the code is written by AI? Will code quality still matter, or only "verifiable correctness"?

To avoid getting bogged down in the limitations of today's AI, let's project 30 years into the future.

What will seem ridiculous in 30 years? Not working with AI agents as integral team members, coding manually or "artisanally," and remaining at the same level of abstraction (that is, typing text into files).

What will still make sense? Solving problems using software, understanding and learning a domain, and contributing our knowledge to the business.

This leads to a powerful metaphor: in the future, manual coding will be like doing Sudoku or puzzles. It will be a stimulating mental exercise, a hobby for those who enjoy it, but not a professional necessity for building the vast majority of software.

Look, I know not everyone has had their "wow" moment yet. Your mileage with AI coding assistants might vary wildly from mine. Maybe you've tried them and found them frustrating, or maybe they just don't fit your workflow. That's completely valid.

But here's why I'm making this 30-year projection: I want to sidestep the debate about whether AI "works" for programming right now. That debate is too tied to individual experience and current limitations. What I care about is the trend, and the trend seems clear and irreversible. The investment, the adoption, the sheer momentum behind this technology doesn't look like a passing fad.

So whether you've had your moment of "this changes everything" or you're still skeptical, here's what we can't avoid: the change is already here. Organizations are already restructuring around it. Budgets are already shifting. Roles are already being redefined.

Which brings us to the critical question.

A Call to Action: Who Will Reinvent Our Profession?

The shift we are experiencing is not a minor innovation like blockchain or whatever the latest hype cycle is selling us. According to the models of economist Carlota Pérez (whose work on technological revolutions analyzes patterns across centuries of industrial transformation), this is a technological revolution on the scale of the steam engine or mass production. We are at a turning point that will redefine everything.

This brings us to the central question: Who do we want to reinvent our profession?

We have two options:

  • Option A: The consultants. Firms like McKinsey, whose articles on developer productivity reveal a fundamental misunderstanding of the nature of software.
  • Option B: Us. The community of practitioners who truly understand software's nature, people who have embraced Extreme Programming, Lean, and Software Craftsmanship.

This choice is being made right now. Not in five years. Not when "AI matures." Now. Every day that passes, more organizations are adopting AI productivity metrics designed by people who've never shipped production code. More engineering teams are being restructured by consultants who think code generation is just "faster typing." More junior developers are being evaluated by KPIs that measure activity instead of impact. The window to shape this transformation according to our values is closing.

The stakes are higher than a simple preference. This is a fight for the soul of software development. If we, the craftspeople, don't lead this transformation, the consultants will define the future of our work. And I am not going to like the profession they design for us.

But choosing Option B—choosing us—means more than just declaring we're in charge. It means accepting responsibility for each other. It means the experienced developers who mastered previous paradigms must guide those entering the field during its most chaotic moment. It means creating spaces where experimentation is safe, where failure is learning, and where knowledge flows freely instead of being hoarded. This isn't optional. If we don't take care of our community, we don't deserve to lead this transformation.

Revisiting Our Core Principles: Software is a Means, Not an End

The first line of the Agile Manifesto is key: "We are uncovering better ways of developing software..." It was never a static set of discovered truths. It has always been about continuous learning and adaptation. The prime directive has always been to satisfy the customer by solving their problems.

This brings us to the most important principle to remember today: Software is the medium, not the end. Our mission was never just to write code. It was to solve problems and create impact. If coding is destined to become a hobby like Sudoku, then our professional identity must be rooted in something deeper. The over-specialization that led to rigid roles like "frontend developer" or "DevOps engineer" was a mistake, a mistake that AI now gives us the power to correct.

Consider the software development "Food Chain", which maps the workflow from Opportunity Selection to Validating Impact.

This visual tells a crucial story. The top section, outlined in red and marked with a skull and crossbones, represents the "danger zone" of siloed, waterfall-style handoffs. This is where individuals are disconnected from the customer and the problem they are trying to solve. In contrast, the bottom section, outlined in green, is the "safe zone" where integrated, cross-functional teams own the entire value stream from beginning to end.

The implication is clear: AI makes it painfully obvious that roles isolated in the "build" phase are the most vulnerable. To remain relevant, we must move closer to the beginning (understanding the problem) and the end (validating the impact).

The Mindset of Radical Detachment

To navigate this new era, we need the intellectual humility to empty our own cups. We must unlearn what we think we know to make space for new ideas.

You Are Not Your Code

Because the nature of software is to change, we must practice radical detachment from our creations. Detach from your beautiful code, your elegant designs, and your favorite patterns. Holding on too tightly creates rigidity, turning what should be flexible clay into a brittle relic. Our business is not building finished solutions; it is evolving solutions over time.

The New Dynamics of Managing Complexity

With AI, the friction to generate new ideas, experiments, and complexity is almost zero. The process of software development will no longer be a slow, incremental growth of a single snowball. Instead, it will be a rapid cycle of expansion and contraction: exploring dozens of options in parallel, then radically deleting and simplifying to keep only what works. Managing this explosion of complexity will become one of the most critical skills we possess.

The Great Multiplier: AI in the Forest vs. the Desert

Beth Andres-Beck and Kent Beck describe two contrasting environments for building software: the desert and the forest.

The Desert is characterized by constant urgency, silos, poor testing, and misunderstood practices. It's a place of chaos and anxiety. The Forest is characterized by good practices, technical excellence that enables evolution, and close collaboration with the business. It's a place of sustainable growth.

AI is an indiscriminate multiplier. Its effect depends entirely on the environment it's applied to.

In the Desert, AI multiplies chaos. It becomes a factory for producing "fast trash," turning a technical debt problem into technical bankruptcy. In the Forest, AI multiplies discipline. It creates a virtuous cycle. Good practices like TDD, clean code, and good documentation provide the AI with better context, making it more effective. In turn, the AI makes it easier and faster to apply these good practices, turning them into unprecedented velocity and quality.

This leads to a paradigm shift. For the first time in our industry's history, the economic argument for software craftsmanship is no longer a nuanced discussion about long-term TCO. It is an immediate, undeniable, and exponential driver of value. AI makes excellence profitable in the short term.

Essential Practices for the AI-Augmented Developer

Certain engineering practices become supercharged in the AI era.

Modular architecture is essential. The rapid expansion-contraction cycle of development is only manageable within a modular architecture. Modularity provides the firewalls necessary to run dozens of parallel experiments without the entire system collapsing into chaos.

Clean code is crucial. Semantic, understandable code is essential for the AI to grasp business logic and provide meaningful assistance.

Quality and automated testing is non-negotiable. It is the only way to safely validate the massive volume of code AI can generate.

Small steps remain important. The principle of working in small, verifiable increments remains, but we must be open to the idea that the "size" of a step might change dramatically.

The Developer Laziness Scale

Think of your interaction with AI on a "laziness scale":

  • 100%: You write a prompt and blindly accept the output.
  • 80%: You glance over the code for obvious errors.
  • 50%: You review every line of AI-generated code carefully.
  • 20-30%: You code mostly by hand, using AI for autocompletion.
  • 0%: You write everything manually, artisanally.

The meta-skill is to treat this scale like a gear shift, consciously choosing the right level of automation and oversight for the road ahead. This requires retaining deep, low-level knowledge even when you aren't always using it directly.

FAAFO and Radical Simplification

The book Vibe Coding by Gene Kim and Steve Yegge introduces the FAAFO framework: a style of work that is Fast, Ambitious, Autonomous, Fun, and creates Optionality. AI is the engine that makes this possible, allowing us to tackle legacy refactors or build prototypes that once seemed impossible.

However, this power comes with a critical discipline. The ability to be Ambitious and create Optionality must be balanced with an equal commitment to radical simplification and deletion. This is where the discipline of radical detachment becomes a tactical necessity. We must detach from failed experiments as quickly as we create them.

Our Evolving Roles in the New Value Chain

AI is turning the "Build" and "Test" phases of software development into a commodity. The enduring human value, and therefore career security, lies at the extremes of the value chain: deeply understanding the "why" at the beginning and rigorously validating the impact at the end.

The bottleneck is shifting to the "fuzzy front end" (Opportunity Selection, Requirements Planning) and the "validated back end" (Running the system, Validating impact, and gathering Feedback). This highlights two critical skill areas for the future.

First, a product mindset. We need to deeply understand the problem, its context, and its intended impact. The challenge is no longer "is the code good?" but "does the system achieve the desired effect?"

Second, architectural and engineering practices. The responsibility for the system's results doesn't disappear; it intensifies. Skills in architecture, security, scalability, and performance become more critical because we will be making these decisions more frequently. AI can accelerate code generation, but the system will collapse without sound modularity and simplicity.

We Are All Beginners Again

Here's the uncomfortable truth: no matter how many years you have in this industry, we are all beginners in the Age of AI-augmented software delivery.

I've been doing this for about 30 years. That experience gives me some advantages: pattern recognition, intuition about what might work, the confidence to experiment without fear. But it doesn't exempt me from the uncertainty. When I'm pair-programming with Claude, I'm learning just as much as someone who started coding last year. The difference is that I have the luxury to be wrong, to throw away experiments, to admit I don't know.

But what about the people entering our field right now? They're walking into the most turbulent period our profession has ever seen, at a moment when the rules are being rewritten in real time. We, the experienced ones, have a responsibility to them that we cannot abdicate.

We must create safe spaces where they can learn without fear: on our teams, at local meetups, in conferences. We must share our experiments, even the failures. We must run hackathons where we figure this out together, just as we did with unit testing and Agile practices in the early 2000s. The difference is that now, the experienced among us must actively lower the barriers for those just starting, because the barriers are higher than they've ever been.

This is not charity. This is survival. If we hoard knowledge, if we gate-keep, if we let newcomers drown in the chaos, we will not have a community strong enough to fight for the soul of our profession. The consultants will win by default.

Our greatest strength has always been our community. Now is the time to prove it.

Your Path Forward: An Action Plan

So, what can you do right now to prepare?

  1. Move down the food chain. Get closer to the customer and the problem. Don't just ask "what"—always ask "why." Understand the business impact you are trying to create.
  2. Evolve from specialist to generalist. The era of the generalist is here. You don't need to be an expert in everything, but you need to learn enough about adjacent disciplines to be effective. AI can fill in the deep, specific details (like framework syntax), but you need to connect the concepts.
  3. Imagine our future tools. We should be building our own AI agents, encoded with our community's knowledge. Imagine a "gardener" agent that refactors code overnight, a "garbage truck" agent that identifies and removes dead code, or a performance-tuning agent that pinpoints bottlenecks. These tools are within our reach.

Conclusion: We Can't Avoid the Storm, So Let's Surf the Wave

We have the greatest opportunity in the history of our profession to finally build software in "the forest," a place where technical excellence and business impact are perfectly aligned. This requires adopting a product mindset and being willing to reinvent our best practices. It means letting go of what we think we know about software delivery, accepting that AI agents will become teammates whether we like it or not, and leaning heavily on our community as we navigate this together.

Of course, I don't have all the answers, and it's not all sunshine. There are serious issues that we, as a community, need to confront: the addictive reward cycle of working with AI, the consolidation of power in a few large tech companies, the challenge of training junior developers, and the environmental impact. These are important topics for future discussion.

But we can't let these challenges paralyze us. The change is coming. We can't avoid the storm, and honestly, we don't want to.

I've made my choice. I'm experimenting, sharing my failures as much as my wins, and making space for others to do the same. But I can't do this alone, and neither can you.

So here's what I'm asking: will we, as a community, step up and shape this transformation? Will we create the spaces where we can learn and experiment safely? Will we build the tools that encode our values instead of waiting for someone else to define them?

Or will we let this moment slip away while we debate whether AI is "good enough" yet?

The wave is here. The question isn't whether to surf it. The question is whether we'll surf it together, or watch from the shore while others ride it for us.


About This Article

This article is based on my keynote "Desapego Radical en la Era de la IA" (Radical Detachment in the AI Era), delivered at Software Crafters Barcelona in October 2025. The talk was given in Spanish and has been expanded and adapted for this written version. You can watch the original keynote and access the slides at eferro.net.

References

Saturday, November 29, 2025

Cursor Commands Added to augmentedcode-configuration

I've just updated my augmentedcode-configuration repository with Cursor commands that codify my XP/Lean development workflows.

What's new:

Added .cursor/commands/ with 8 reusable command patterns:

  • plt-code-review — Review pending changes (tests, maintainability, project rules)
  • plt-increase-coverage — Identify and test high-value untested paths
  • plt-plan-untested-code — Create strategic plans to close coverage gaps
  • plt-predict-problems — Predict production failure points proactively
  • plt-technical-debt — Catalog, classify, and prioritize technical debt
  • plt-mikado-method — Apply Mikado Method for safe incremental refactoring
  • plt-xp-simple-design-refactor — Refactor using XP Simple Design principles
  • plt-security-analysis — Pragmatic security risk assessment

Purpose:

These commands codify recurring workflows as reusable patterns. Instead of ad-hoc prompting, they provide consistent guidance for the AI to:

  • Work in small, safe steps
  • Focus on tests and maintainability
  • Apply Lean/XP principles
  • Follow project-specific conventions

They work alongside the existing base rules, creating a more complete configuration for AI-augmented development.

👉 Check them out: .cursor/commands/

This is an update to my original post: My Base Setup for Augmented Coding with AI

Sunday, November 09, 2025

Pseudo TDD with AI

Exploring Test-Driven Development with AI Agents

Over the past few months, I've been experimenting with a way to apply Test-Driven Development (TDD) by leveraging artificial intelligence agents. The goal has been to maintain the essence of the TDD process (test, code, refactor) while taking advantage of the speed and code generation capabilities that AI offers. I call this approach Pseudo TDD with AI.

How the Process Works

The AI agent follows a set of simple rules:

  1. Write a test first.
  2. Run the test and verify that it fails.
  3. Write the production code.
  4. Run the tests again to verify that everything passes.

I use the rules I defined in my base setup for augmented coding with AI. With these base rules, I can get both the Cursor agent and Claude Code to perform the TDD loop almost completely autonomously.

The refactoring part is not included automatically. Instead, I request it periodically as I observe how the design evolves. This manual control allows me to adjust the design without slowing down the overall pace of work.

Confidence Level and Limitations

The level of confidence I have in the code generated through this process is somewhat lower than that of TDD done manually by an experienced developer. There are several reasons for this:

  • Sometimes the agent doesn't follow all the instructions exactly and skips a step.
  • It occasionally generates fewer tests than I would consider necessary to ensure good confidence in the code.
  • It tends to generalize too early, creating production code solutions that cover more cases than have actually been tested.

Despite these issues, the process is very efficient and the results are usually satisfactory. However, it still doesn't match the confidence level of fully human-driven TDD.

Supporting Tools

To compensate for these differences and increase confidence in the code, I rely on tools like Mutation Testing. This technique has proven very useful for detecting parts of the code that weren't adequately covered by tests, helping me strengthen the reliability of the process.

Alternative Approaches Explored

In the early phases of experimentation, I tried a different approach: directing the TDD process myself within the chat with the AI, step by step. It was a very controlled flow:

"Now I want a test for this."
"Now make it pass."
"Now refactor."

This method made the process practically equivalent to traditional human TDD, as I had complete control over every detail. However, it turned out to be slower and didn't really leverage the AI's capabilities. In practice, it worked more as occasional help than as an autonomous process.

Next Steps

From the current state of this Pseudo TDD with AI, I see two possible paths forward:

  1. Adjust the rules and processes so the flow comes closer to human TDD while maintaining AI speed.
  2. Keep the current approach while observing and measuring how closely it actually approximates a traditional TDD process.

In any case, I'll continue exploring and sharing any progress or learnings that emerge from this experiment. The goal is to keep searching for that balance point between efficiency and confidence that collaboration between humans and AI agents can offer.

Related Content

My Base Setup for Augmented Coding with AI

Repository: eferro/augmentedcode-configuration

Over the last months I've been experimenting a lot with AI-augmented coding — using AI tools not as replacements for developers, but as collaborators that help us code faster, safer, and with more intention.

Most of the time I use Cursor IDE, and I complement it with command-line agents such as Claude Code, Codex CLI, or Gemini CLI.

To make all these environments consistent, I maintain a small open repository that serves as my base configuration for augmented coding setups:

👉 eferro/augmentedcode-configuration

Purpose

This repository contains the initial configuration I usually apply whenever I start a new project where AI will assist me in writing or refactoring code.

It ensures that both Cursor and CLI agents share the same base rules and principles — how to write code, how to take small steps, how to structure the workflow, etc.

In short: it's a simple but powerful way to keep my augmented coding workflow coherent across tools and projects.

Repository structure

augmentedcode-configuration/
├── .agents/
│   └── rules/
│       ├── base.md
│       └── ai-feedback-learning-loop.md
├── .cursor/
│   └── rules/
│       └── use-base-rules.mdc
├── AGENTS.md
├── CLAUDE.md
├── GEMINI.md
├── codex.md
└── LICENSE


.agents/rules/base.md

This is the core file — it defines the base rules I use when coding with AI.

These rules describe how I want the agent to behave:

  • Always work in small, safe steps
  • Follow a pseudo-TDD style (generate a test, make it fail, then implement)
  • Keep code clean and focused
  • Prefer clarity and maintainability over cleverness
  • Avoid generating huge chunks of code in one go

At the moment, these rules are slightly tuned for Python, since that's the language I use most often. When I start a new project in another language, I simply review and adapt this file.

🔗 View .agents/rules/base.md


.agents/rules/ai-feedback-learning-loop.md

This file defines a small feedback and learning loop that helps me improve the rule system over time.

It contains guidance for the AI on how to analyze the latest session, extract insights, and propose updates to the base rules.

In practice, I often tell the agent to "apply the ai-feedback-learning-loop.md" to distill the learnings from the working session, so it can generate suggestions or even draft changes to the rules based on what we learned together.

🔗 View .agents/rules/ai-feedback-learning-loop.md


.cursor/rules/use-base-rules.mdc

This small file tells Cursor IDE to use the same base rules defined above.

That way, Cursor doesn't have a separate or divergent configuration — it just inherits from .agents/rules/base.md.

🔗 View .cursor/rules/use-base-rules.mdc


AGENTS.md, CLAUDE.md, GEMINI.md, codex.md

Each of these files is simply a link (or reference) to the same base rules file.

This trick allows all my CLI agentsClaude Code, Codex, Gemini CLI, etc. — to automatically use the exact same configuration.

So regardless of whether I'm coding inside Cursor or launching commands in the terminal, all my AI tools follow the same guiding principles.

🔗 AGENTS.md
🔗 CLAUDE.md
🔗 GEMINI.md
🔗 codex.md


How I use it

Whenever I start a new project that will involve AI assistance:

  1. Clone or copy this configuration repository.
  2. Ensure that .agents/rules/base.md fits the project's language (I tweak it if I'm not working in Python).
  3. Connect Cursor IDE — it will automatically load the rules from .cursor/rules/use-base-rules.mdc.
  4. When using Claude Code, Codex, or Gemini CLI, they all read the same base rules through their respective .md links.
  5. During or after a session, I often run the AI Feedback Learning Loop by asking the agent to apply the ai-feedback-learning-loop.md so it can suggest improvements to the rules based on what we've learned.
  6. Start coding interactively: I ask the AI to propose small, incremental changes, tests first when possible, and to verify correctness step by step.

This results in a workflow that feels very close to TDD, but much faster. I like to call it pseudo-TDD.

It's not about strict process purity; it's about keeping fast feedback loops, learning continuously, and making intentional progress.

Why this matters

When working with multiple AI agents, it's surprisingly easy to drift into inconsistency — different styles, different assumptions, different "personalities."

By having one shared configuration:

  • All tools follow the same Lean/XP-style principles.
  • The workflow remains consistent across environments.
  • I can evolve the base rules once and have every agent benefit from it.
  • It encourages me (and the agents) to think in small steps, test early, and refactor often.
  • The feedback learning loop helps evolve the rule system organically through practice.

It's a small setup, but it supports a big idea:

"Augmented coding works best when both human and AI share the same working agreements — and continuously improve them together."

Adapting it

If you want to use this configuration yourself:

  1. Fork or clone eferro/augmentedcode-configuration.
  2. Adjust .agents/rules/base.md for your preferred language or conventions.
  3. Point your IDE or CLI agents to those files.
  4. Use .agents/rules/ai-feedback-learning-loop.md to help your agents reflect on sessions and evolve the rules.
  5. Experiment — see how it feels to work with a single, unified, and self-improving set of rules across AI tools.

Next steps

In an upcoming post, I'll share more details about the pseudo-TDD workflow I've been refining with these agents — how it works, what kinds of tests are generated, and how it compares to traditional TDD.

For now, this repository is just a small foundation — but it's been incredibly useful for keeping all my AI coding environments consistent, adaptive, and fast.

Related Content

Mutation Testing: When "Good Enough" Tests Weren't

For weeks, I had been carrying this nagging doubt. The kind of doubt that's easy to ignore when everything is working. My inventory application had 93% test coverage, all tests green, type checking passing. The code had been built with TDD from day one, using AI-assisted development with Claude, Cursor (with Sonnet 4.5, GPT-4o, and Claude Composer), what I like to call "vibecoding". Everything looked solid.

It's not a big application. About 650 lines of production code. 203 tests. A small internal tool for tracking teams and employees. The kind of project where you might think "good enough" is actually good enough.

But something was bothering me.

I had heard about mutation testing years ago. I even tried it once or twice. But let's be honest: it always felt like overkill. The setup was annoying, the output was overwhelming, and the juice rarely seemed worth the squeeze. You had to be really committed to quality (or really paranoid) to go through with it.

This time, though, with AI doing the heavy lifting, I decided to give it another shot.

The First Run: 726 Mutants

I added mutmut to the project and configured it with AI's help. Literally minutes of work. Then I ran it:

$ make test-mutation
Running mutation testing
726/726  🎉 711  ⏰ 0  🤔 0  🙁 0  🔇 15  🔴 0
33.50 mutations/second

Not bad. 711 mutants killed out of 726. That's 97.9% mutation score. I felt pretty good about it.

Until I looked at those 15 survivors.

The 15 Survivors

I ran the summary command to see what had survived:

$ make test-mutation-summary
Total mutants checked: 15
Killed (tests caught them): 0
Survived (gaps in coverage): 15

=== Files with most coverage gaps ===
    5 inventory.services.role_config_service
    4 inventory.services.orgportal_sync_service
    2 inventory.infrastructure.repositories.initiative
    1 main.x create_application__mutmut_6: survived
    1 inventory.services.orgportal_sync_service.x poll_for_updates__mutmut_6: survived
    1 inventory.db.gateway
    1 inventory.app_setup.x include_application_routes__mutmut_33: survived

There they were. Fifteen little gaps in my test coverage. Fifteen cases where my tests weren't as good as I thought.

And remember: this is a 650-line application with 203 tests. If I found 15 significant gaps here, what would I find in a 10,000-line system? Or 100,000?

The thing is, a few months ago, this would have been the end of the story. I would have looked at those 15 surviving mutants, felt slightly guilty, and moved on. The effort to manually analyze each mutation, understand what it meant, and write the specific tests to kill it would have taken days. Maybe a week.

Not worth it for a small internal tool.

But this time was different.

What the Mutants Revealed

Before jumping into fixes, I wanted to understand what these surviving mutants were actually telling me. With AI's help, I analyzed them systematically.

Here's what we found:

In role_config_service (5 survivors):
The service loaded YAML configuration for styling team roles. My tests verified that the service loaded the config and returned the right structure. But they never checked what happened when:

  • The YAML file was missing
  • The YAML was malformed
  • Required fields were absent

The code had error handling for all these cases. My tests didn't verify any of it.

In orgportal_sync_service (4 survivors):
This service synced data from S3. Tests covered the happy path: download file, process it, done. But mutants survived when we:

  • Changed log messages (I wasn't verifying logs)
  • Skipped metadata checks (last_modified, content_length)
  • Removed directory existence checks

The code was defensive. My tests assumed everything would go right.

In database and infrastructure layers (6 survivors):
Similar story. Error paths that existed in production but were never exercised in tests:

  • SQLite connection failures
  • Invalid data in from_db_row factories
  • 404 responses in API endpoints

Classic case of "it works, so I'm not testing the error cases."

The pattern was clear: I had good coverage of normal flows, but my tests were optimistic. They assumed the happy path and left the defensive code untested.

This is what deferred quality looks like at the micro level. Like Deming's red bead experiment (where defects came from the system, not the workers), these weren't random failures. They were systematic gaps in how I verified the system. Every surviving mutant is a potential bug waiting in production, interrupting flow when it surfaces weeks later. The resource efficiency trap: "we already have 93% coverage" feels cheaper than spending 2-3 hours... until you spend days debugging a production issue that a proper test would have caught.

The AI-Powered Cleanup

But this time I had AI. So I did something different.

I asked Claude to analyze the surviving mutants one by one, understand what edge cases they represented, and create or modify tests to cover them. I just provided some guidance on priorities and made sure the new tests followed the existing style.

(The app itself had been built using a mix of tools: Claude for planning and architecture, Cursor with different models for implementation. But for this systematic mutation analysis, Claude's reasoning capabilities were particularly useful.)

In about two or three hours, we had addressed all the key gaps:

  • SQLite error handling: I thought I was testing error paths, but I was only testing the happy path. Added proper error injection tests.
  • Factory method validation: My from_db_row factories had validation that was never triggered in tests. Added tests with invalid data.
  • Edge cases in services: Empty results, missing metadata, nonexistent directories. All cases my code handled but my tests never verified.
  • 404 handling in APIs: The code worked, but no test actually verified the 404 response.

The result after several iterations:

$ make test-mutation
Running mutation testing
726/726  🎉 724  ⏰ 0  🤔 0  🙁 2  🔇 0
30.02 mutations/second
$ make test-mutation-summary
Total mutants checked: 2
Killed (tests caught them): 0
Survived (gaps in coverage): 2

=== Files with most coverage gaps ===
    1 inventory.services.role_config_service
    1 inventory.db.gateway

From 15 surviving mutants down to 2. From 97.9% to 99.7% mutation score.

The coverage numbers told a similar story:

Coverage improvements:
- database_gateway.py: 92% → 100%
- teams_api.py: 85% → 100%
- role_config_service.py: 86% → 100%
- employees_api.py: 95% → 100%
- Overall: 93% → 99%
- Total tests: 203 passing

The Shift in Economics

Here's what struck me about this experience: the effort-to-value ratio had completely flipped.

Before AI, mutation testing was something you did if:

  • You had a critical system where bugs were expensive
  • You had a mature team with time to invest
  • You were willing to spend days or weeks on it
  • The application was large enough to justify the investment

For a 650-line internal tool? Forget about it. The math never worked out.

Now? The math is different. The AI did all the analysis work. I just had to review and approve. What used to take days took hours. And most of that time was me deciding priorities, not grinding through mutations.

The barrier to rigorous testing has dropped dramatically. And it doesn't matter if your codebase is 650 lines or 650,000. The cost per mutant is the same.

The Question That Remains

I've worked in teams that maintained sustainable codebases for years. I know what that forest looks like (to use Kent Beck's metaphor). I also know how much discipline, effort, and investment it took to stay there.

Now I'm seeing that same level of quality becoming accessible at a fraction of the cost. Tests that used to require days of manual work can be generated in hours. Mutation testing that was prohibitively expensive is now just another quick pass.

The technical barrier is gone.

So here's the question I'm left with: now that mutation testing costs almost nothing, will we actually use it? Will teams that never had the resources to invest in this level of testing quality start doing it?

Or will we find new excuses?

Because the old excuse ("we don't have time for that level of rigor") doesn't really work anymore. The time cost has collapsed. The tooling is there. The AI can do the heavy lifting.

What's left is just deciding to do it. And knowing that it's worth it.

What I Learned

Three concrete takeaways from this experience:

1. Line coverage lies, even in small codebases: 93% coverage looked great until mutation testing showed me the gaps. Those 15 surviving mutants were in critical error handling paths. After fixing them, I still had 99% line coverage. But now the tests actually verified what they claimed to test. If a 650-line application had 15 significant gaps, imagine larger systems.

2. AI makes rigor accessible for any project size: What used to be prohibitively expensive (manual mutation analysis) is now quick and almost frictionless. The economics have changed. From 15 survivors to 2 in just a few hours of work, most of it done by AI. This level of rigor is no longer reserved for critical systems. It's accessible for small internal tools too.

3. 99.7% is good enough: After the cleanup, I'm left with 2 surviving mutants out of 726. Could I hunt them down? Sure. Is it worth it? Probably not. They're edge cases in utility code that's already well-tested. The point isn't perfection. It's knowing where your gaps are and making informed decisions about them.

The real win isn't the numbers. It's the confidence. I now know exactly which 2 mutants survive and why. That's very different from having 93% coverage and hoping it's good enough.

This was a small project. If it had been bigger, I probably would have skipped mutation testing entirely (too expensive, too time-consuming). But now? Now I can't think of a good reason not to do it. Not when it costs almost nothing and reveals so much.

I used to think mutation testing was for perfectionists and critical systems only. Now I think it should be standard practice for any codebase you plan to maintain for more than a few months.

Not because it's perfect. But because it's no longer expensive.

And when the cost drops to almost zero, the excuses should too.

The AI Prompt That Worked

When facing surviving mutants, this single prompt did most of the heavy lifting:

"Run mutation testing with make test-mutation. For each surviving mutant, use make test-mutation-show MUTANT=name to see the details. Analyze what test case is missing and create tests to kill these mutants, following the existing test style. After adding tests, run make test-mutation again to verify they're killed. Focus on the top 5-10 most critical gaps first: business logic, error handling, and edge cases in services and repositories."

The key: let the AI drive the mutation analysis loop while you focus on reviewing and prioritizing.

Getting Started

If you want to try this:

  1. Add mutmut to your project (5 minutes with AI help)
  2. Create simple Makefile targets to make it accessible for everyone:
    • make test-mutation - Run the full suite
    • make test-mutation-summary - Get the overview
    • make test-mutation-report - See which mutants survived
    • make test-mutation-show MUTANT=name - Investigate specific cases
    • make test-mutation-clean - Reset when needed
  3. Run it weekly, not on every commit (mutation testing is slow)
  4. Use AI to triage survivors (ask it to analyze and prioritize)
  5. Review the top 5-10 gaps as a pair, decide which matter
  6. Start with one critical module, not the whole codebase

Making it easy to run is as important as setting it up. The barrier is gone. What's stopping you?

When NOT to chase 100%: Those final 2 surviving mutants? They're in logging and configuration defaults that are battle-tested in production. Perfect mutation score isn't the goal. Knowing your gaps is. Focus on business logic and error handling first. Skip trivial code.


About This Project

This application was developed using TDD and AI-assisted development with Claude code and Cursor (using Sonnet 4.5, GPT-5 codex, and Composer1). The mutation testing setup and gap analysis were done with Claude's help using mutmut.

Timeline: The entire mutation testing setup and gap analysis took about 2-3 hours with AI assistance.

Final stats: 649 statements, 208 tests, 99% line coverage, 726 mutants tested, 724 killed (99.7% mutation score).

Related Reading

Monday, November 03, 2025

When AI Makes Good Practices Almost Free

Since I started working with AI agents, I've had a feeling that was hard to explain. It wasn't so much that AI made work faster or easier, but something harder to pin down: the impression that good practices were much easier to apply and that most of the friction to introduce them had disappeared. That many things that used to require effort, planning, and discipline now happened almost frictionlessly.

That intuition had been haunting me for weeks, until this week, in just three or four days, two very concrete examples put it right in front of me.

The Small Go Application

This week, a colleague reached out to tell me that one of the applications I had implemented in Go didn't follow the team's architecture and testing conventions. They were absolutely right: I hadn't touched Go in years and, honestly, I didn't know the libraries we were using. So I did what I could, leaning heavily on AI to get a quick first version as a proof of concept to validate an idea.
The thing is, my colleague sent me a link to a Confluence page with documentation about architecture and testing, and also a link to another Go application I could use as a reference.

A few months ago, changing the entire architecture and testing libraries would have been at least a week of work. Probably more. But in this case, with AI, I had it completely solved in just two or three hours. Almost without realizing it.

I downloaded the reference application and asked the AI to read the Confluence documentation, analyze the reference application, and generate a transformation plan for my application. Then I just asked it to apply the plan, no adjustments needed, just small interactions to decide when to make commits or approve some operations. In just over two hours, and barely paying attention, I had the entire architecture changed to hexagonal and all the tests updated to use other libraries. It felt almost effortless.

It was a small app, maybe 2000 to 3000 lines of code and around 50 tests, but still, without AI, laziness would have won and I would have only done it if it had been absolutely essential.

The cost of keeping technical coherence across applications has dropped dramatically. What used to take serious effort now happens almost by itself.

The Testing That Stopped Hurting

A few days later, I encountered another similar situation, this time in Python. Something was nagging at me: some edge cases weren't well covered by the tests. I decided to use mutmut, a mutation testing library I'd tried years ago but usually skipped because the juice rarely seemed worth the squeeze.

This time I threw in the library, got it configured in minutes with AI's help, and then I basically went on autopilot: I simply generated the mutations and told the AI to go, one by one, analyzing the mutations and creating or modifying the necessary tests to cover those cases. This process required almost no effort from me. The AI was doing all the heavy lifting. I just prioritized a few cases and gave the tests a quick once-over, simply to check that they followed the style of the others.

In a couple of hours, the change in feeling was complete. Night and day. My confidence in the project's tests had shot up and the effort? Practically nothing.

The Intuition That Became Visible

These two examples, almost back-to-back, confirmed the intuition I had been carrying since I started working with AI agents: the economy of effort is changing. Radically.

Refactoring, keeping things coherent, writing solid tests, documenting decisions... None of that matters less now. What has changed is its cost. And when the cost drops to nearly zero, the excuses should vanish too.

If time and effort aren't the issue anymore, why do we keep falling into the same traps? Why do we keep piling on debt and complexity we don't need?

Perhaps the problem isn't technical. Perhaps the problem is that many teams have never really seen what sustainable code looks like, have never experienced it firsthand. They've lived in the desert so long they've forgotten what a forest looks like. Or maybe they never knew in the first place.

Beth Andres-Beck and Kent Beck use the forest and desert metaphor to talk about development practice. The forest has life, diversity, balance. The desert? Just survival and scarcity.

For years I've worked in the forest. I've lived it. I know it's possible, I know it works, and I know it's the right way to develop software. But I also know that building and maintaining that forest was an expensive discipline. Very expensive. It took mature teams, time, constant investment, and a company culture that actually supported it.

Now, with AI and modern agents, building that forest costs almost the same as staying in the desert. The barrier has dropped dramatically. The barrier isn't effort or time anymore. It's just deciding to do it and knowing how.

The question I'm left with is no longer whether it's possible to build sustainable software. I've known that for years. The question is: now that the cost has disappeared, will we actually seize this opportunity? Will we see more teams moving into that forest that used to be out of reach?

Related Content