From resistance to co-creation: my journey with AI coding tools
The diff keeps growing. I scroll through it and lose count of how many files have changed. New abstractions that nobody asked for, interfaces wrapping things that didn’t need wrapping, helper classes for problems I didn’t have. The feature I actually needed still isn’t working. I’d asked the AI to help me build something, and what I got back was a small city of code I didn’t understand and didn’t want.
To understand how I ended up there, and how I got out of it, let’s go back to when I refused to use AI at all, and how I learned to accept it.
I love coding
Writing code has always been more like a craft to me. I genuinely enjoy the process: sitting with a problem, turning it over, finding the edges of it. The satisfaction of figuring out the (supposedly) best way of implementing something. Shipping features felt good, but the tinkering, experimenting and trial-and-error that we do to get there were the reasons I liked programming.
When AI coding tools started getting serious attention, my instinct was to stay away. It felt like a shortcut that would hollow out the part I actually liked. I wasn’t worried about being replaced or anything like that (I didn’t think it was a concern at that point). I was worried about no longer being part of the process and not really understanding what we were building. What was the point in shipping faster if we don’t know what we are shipping?
So I didn’t use it. For a while, that felt the right thing to do. I was still getting my things done, and everything was fine. Then it started to feel stubborn… More and more people I know were using it. They told me about hours saved on bug investigations, or about parsing thousands of lines of logs to find a blip in the system. I decided it was time to at least understand what I was not being a part of.
Baby-steps with AI
My first experiment was narrow on purpose. I used AI to write unit tests.
That might sound boring, but it was smart. Tests are low-stakes in a specific way: you can read the output, you know exactly what “correct” looks like, and you don’t have to trust the AI’s judgement about design. You’re verifying behaviour you already understand. If it got something wrong, I’d catch it easily. The risk was low.
It worked well. So I pushed a bit further and started using it to review PRs, which was another job where the AI’s role is clearly (or so I thought) secondary. A second opinion, not a replacement. It would catch things I’d missed and suggest improvements I hadn’t considered. Good.
Then I used it for small features. Contained things. A new endpoint, a simple refactoring, something I could hold in my head and verify against a clear spec. That worked too.
I was building trust incrementally, and I knew it. Each step was essentially: “Okay, that worked, what’s the next thing I’m willing to try?” It didn’t really feel like I was converting to AI or anything like that, it was more like I was slowly becoming comfortable delegating small tasks to it, like working with a junior developer or someone new to a project.
I hate AI
The next thing I tried was a larger and more complex feature. That’s where it all fell apart for me.
The AI started generating code. A LOT of it. Abstractions I hadn’t asked for layering on top of other abstractions, files being modified that had nothing to do with what I needed. I kept trying to course-correct (“no, just focus on this part”) and it would acknowledge me and then keep going in the same direction. The diff grew to something I couldn’t read in a sitting.
And the feature still didn’t work.
The problem wasn’t exactly that the AI was writing bad code. It was that it wasn’t working on the right problem. It was optimising for something, completeness maybe, or a kind of architectural tidiness, that wasn’t what I needed. I needed a specific thing done. What I got was a lot of adjacent things done instead, wrapped in a structure that made the whole thing harder to reason about.
I walked away. Not permanently, but I didn’t trust it for a while after that. The failure confirmed something I had in the back of my mind: that AI was more like a “smarter” IDE, and that if you handed it a more complicated task, you would end up worse off than when you started. More code, less clarity, and the original problem is still sitting there.
Let’s be friends, AI
Something had to change. It can’t be that I was the only one who could see how “inefficient” using AI for complex real-world problems was. I’m not that smart…
What changed eventually wasn’t really the tools or the models. It was how I used them and my mindset approaching the way I do things. After more than a decade doing things my way, learning to trust myself and to earn each line of code, letting go wasn’t a card I was ready to play.
The biggest shift was understanding how to actively manage context. An AI working on a large feature with an open-ended prompt will do what it does: generate possibilities, cover cases and add structure. That’s its nature. My job is to constrain that. Be specific about scope, define what “done” looks like, give it a small enough problem that it can actually solve it. Learning to work on a plan and, collaboratively, iterate on it until I was happy with the approach.
I also started paying more attention to which model I was using for which kind of task. Some tasks benefit from something that reasons carefully and works through trade-offs. Others need fast, accurate execution of something already well-defined. Getting that pairing right makes a real difference.
Then it struck me… the way AI and I are working together to solve a problem isn’t too much different to the way I would approach a problem with co-workers. Building a shared understanding of the problem, working on a shared domain language that everyone in the team understands, and eventually breaking the work into more manageable chunks that the team can share is the MO we have been using in software development for a long time.
The key difference here is that there wasn’t necessarily a team working together. It was my virtual co-worker and I, but my co-worker was able to code as fast as anyone, to read the codebase in more detail than anyone, and never seemed to have a bad day (although some days I swear it feels like Claude needs a day off or something, given how many times their API leaves me hanging).
So the answer I was looking for was never “let AI do everything” but “work WITH it to get things done”.
How I have been using AI
I tried a few different environments and workflows. IDE integrations first (IntelliJ, then VS Code), and eventually the CLI. Each one taught me something about how I wanted to work.
The IDE integrations are comfortable, but they put you in a reactive mode: you ask, it responds, you accept or modify. The CLI felt different. More like directing a collaborator than invoking a feature. You’re closer to the session, more aware of what the model is doing and why. It also helped me to let go of wanting to check every single line of code being written (although I still struggle with that impulse to review everything).
The workflow I eventually landed on was: ask Claude to write a plan before writing any code. Not a rough sketch, a real plan. I’d start giving the context of what we’re building, why it was important, the approach we should take, and let it suggest things. Then I’d actually read it. Not skim, properly engage with it. Push back on things that felt wrong. Eventually, I (we) would break that plan into tasks. I’d often have that plan saved as a markdown document somewhere on my laptop, or even checked into the git repo.
Only then would I (or should I say Claude) start to implement the feature.
This sounds like overhead, but in practice, it saves time. The loop of despair when it feels like I need to stop the AI agent before it destroys the project only happened because I skipped this step. I handed over a vague problem and got back a vague solution at scale. The plan step forces alignment before code starts flowing. It also keeps me in the loop. I understand what’s about to happen before it happens, and more importantly, the why.
I’ve been experimenting with extensions to that workflow, and the tools that have stuck are the ones that fit into it rather than trying to replace it.
The first was Superpowers, a plugin that bundles a structured set of workflows and skills for working with Claude. What drew me to it was that it reinforced the same discipline I’d been building toward on my own: plan first, review before implementing, stay deliberate about what you’re handing off. It naturally fit into the workflow I was refining.
The second was claude-mem, which solves a problem I hadn’t properly named until I ran into it: memory across sessions. If you’re working on something over multiple days, context decays. The assistant doesn’t remember what you decided yesterday, what constraints you’d established, what approaches you ruled out. You end up re-explaining things and re-litigating decisions you thought you’d already made. claude-mem builds up a persistent picture of the project that carries forward session to session, so you’re not starting cold every time you open a new conversation. I know the default Claude memory also does that, but claude-mem does it better, and more importantly, using fewer tokens (yes, token anxiety is a real thing!).
The third experiment was using Codex as a second pass on things Claude had implemented. Essentially, having one AI review another’s output. It sounds redundant, and sometimes it is. But it catches something specific: places where the implementation drifted from the original intent, or where a locally reasonable design choice doesn’t hold up in the context of the rest of the codebase. A second perspective on AI-generated code has been more useful than I expected.
None of these tools are magic (sometimes they are). What they share is that they fit into a workflow where I’m still the one making decisions, or at least influencing the decisions.
What’s next?
I’m still learning. I am sure there are many things that can be done better. But for now I am happy where I am.
I am learning to find joy in this new era where the lines of code I write are less important. It still makes me sad to see that, more and more, I write less code and more prompts and markdown files. I wonder if this is how programmers felt in the old days, when compilers started to evolve, and higher-level languages were developed.
But at least, I feel like I still add value to the process. I don’t think an AI agent could completely replace me (yet). And I am definitely more productive these past few months than I have ever been.
There’s a term that’s been floating around, “vibecoding”, that describes something I want to avoid. The idea of feeding a prompt and accepting whatever comes out. Moving fast without thinking too hard about what’s being built. The vibe is good, the understanding is optional. For some things, that’s fine. For anything I care about, it isn’t.
What I’ve found instead is that the sweet spot for me is what I call co-creation. I’m still the one at the steering wheel, making the decisions about scope and direction, pushing back when things drift. The AI does a lot of the drafting, but the thinking is still mine to do (sometimes). You’ve got a collaborator who can move faster than you can, and the job is to point that collaborator at the right thing.
The resistance I felt at the start came from a real concern: that using AI would mean giving up the part of the work I found meaningful. I don’t think it has to. The craft is still there. It just takes more deliberate effort to hold onto, and that effort is itself part of the craft. At least for now…