Sitemap

NetWatchly: From Vibe Coding to Structured AI Development

11 min readMay 25, 2026

--

Press enter or click to view image in full size

NetWatchly is a simple website monitoring service. Nothing fancy. But the real story is not the product itself — it is how it was built.

How AI Got Me

I was at QCon London 2026 and most of the talks were great. But I got sick of all that AI hype. AI buzzwords were everywhere, even where they didn’t make any sense. For example, I was at a talk about eBPF, which is a really cool technology. The presentation was great, but the only thing related to AI was in the demo. They demonstrated how eBPF can be used for pre and post processing of commands like making an HTTP request. And instead of processing simple HTTP call made with curl, they processed the call AI agent makes to its API. "Enough" to justify the AI buzzword in the description.

But after a while, I could not stand aside and watch the world change. I had to admit — it is a revolution. So I decided to give AI-driven development a try.

I have spent more than twenty years building software in Java. This time around, I wanted to try something different — to see if I could build a real product with AI doing all the implementation work, at least from a coding perspective. Also, I wanted to explore the magic world of agents, skills, and tools. Previously, I had used AI coding agents for simple tasks, but never for an entire project, and I didn’t know how to organize and structure the process. So I decided to experiment and see how it would go.

From the start, I set one rule: I would not manually write application code. Instead, I was going to play the role of a product manager, maybe a team lead — writing specifications and overseeing the implementation process. I might manually test from time to time, but I would not write code myself.

I chose technologies I barely knew: Go for the backend and React/TypeScript for the frontend. There were two reasons for that. First, I wanted to see how far I could get without even looking at the code. I knew that if I chose Java, I would be tempted to jump in and change the code myself. Second, almost equally important, I had to optimize resource usage since I decided to host on Railway on a minimal budget. It seems that the happy days of developers not caring much about resource utilization and memory footprint are coming to an end. GPUs and memory became surprisingly expensive once humanity discovered it could use AI to generate infinite amounts of dancing chicken videos. So I had to choose technologies that are not too resource hungry. Go and React/TypeScript (served as static assets) seemed like great choices for that.

The Honeymoon Phase: Vibe Coding

The first phase was surprisingly chaotic and extremely productive. Using GitHub Copilot and mostly Claude Sonnet models, I could describe features in a few sentences and receive working implementations within minutes. Monitoring logic, dashboards, authentication, APIs — features appeared faster than I could properly validate them. I was not even instructing the AI to write tests, I didn’t have any code checks, no reviews, nothing — just me confirming the code works. And those were the happy days, when I was just adding features and not worrying about quality, security, or maintainability. But it was exciting to see how quickly I could build something with the help of AI.

Vibe coding is nice because it can deliver results fast. But very soon, as the project grows, that stops being enough. Problems include lack of reviews, validation, testing, PR handling, and instructions that need to be repeated in every session. Additionally, it’s hard to produce the expected behavior without a tight set of instructions. That leads to poor code, lots of bugs, and headaches.

The Reality Check: Why Vibe Coding Doesn’t Scale

At a certain point, I realized I needed a far more structured approach. The project was growing, the codebase was getting bigger and more complex, and inconsistent implementations started appearing. Some features were implemented with tests, some without. Some had good error handling, some didn’t. Some decisions were genuinely wrong. To give one example, one page already loaded 24 hours of monitoring data by default, and the UI only needed additional buttons for zooming into the last 1, 6, or 12 hours. Instead of filtering the already loaded data on the frontend, the implementation introduced separate API endpoints and additional database queries for every time range. The feature worked correctly, but it clearly showed that technically working solutions are not always the right architectural solutions. Over time it became obvious that the AI agent needed much more than simple prompts in order to deliver reliable code consistently. A larger and more complex codebase required careful planning, implementation, reviews, testing, and architectural oversight. It also required the AI agent to have deep knowledge of the project itself. In other words, the structured process became a must. Vibe coding was not working well anymore.

Incidentally, around the same time, token usage for the Sonnet model in GitHub Copilot skyrocketed. I managed to burn through my entire monthly budget in several days without realizing it. So I decided to move from GitHub Copilot to Claude Code.

Enforcing Order: My SDD Workflow

I started by documenting the current state of the project: architecture, functional and non-functional requirements, basic coding guidelines, etc. Then I invested quite some time in writing instructions, skills, and agents. I referenced the documentation I created so it became part of what the AI agent is aware of at all times. This effort required a lot of back and forth until I got it right — quite exhausting, but worth it. Finally, I introduced a structured implementation workflow based on Specification-Driven Development (SDD).

Claude Instructions

Eventually, I ended up with the following setup:

  • Main CLAUDE.md in the root of the repo.
  • Both modules in my repo have their own CLAUDE.md files maintained by Claude itself. They contain the basic source layout, key patterns, startup commands, etc. They help the agent understand the codebase without reading the code itself, saving tokens and speeding up the process.
  • 3 skills: coder (general coding instructions), backend-go (Go-specific instructions), and frontend-react (React-specific instructions).
  • 3 rules: validation (how to perform code validation), git-rules (how to use Git), and github-operations (how to handle PRs, issues, etc. on GitHub).
  • 2 agents: code-reviewer (reviews code, gives feedback, approves PRs) and ui-tester (uses Chrome DevTools MCP for UI testing).

A few tips for writing instructions that actually worked best for me:

  • The fewer instructions, the better. The more you have, the more likely they are to be ignored. Try to keep only the most important ones and make them as concise as possible.
  • Do not write “default” instructions. There is no point in stating things like “write good code” or “follow best practices”. Instead, write specific instructions for things you want done in a particular way. For example, I have instructions for Git commit formatting because I want commits to follow a specific convention.
  • Ask the agent why it did something in a way you did not expect. The answers can be very helpful. Once, I asked an agent about this, and it explained that it chose a certain approach because there were conflicting instructions and it followed one of them. A perfectly legitimate answer, so I fixed my instructions.
  • Use capital letters, bold text, emojis, and other formatting to highlight important instructions. For example, I have a big red warning not to commit code without my approval, while other Git commands do not require approval.
  • Don’t use (sub)agents unless you need a fresh pair of eyes. Subagents can be useful because they run in a separate context (which is both good and bad), but for short-running tasks, it’s overkill to start another context. In my case, I use separate agents for code review and UI testing.
  • Favor rules over skills. I noticed that rules are more likely to be followed than skills.
  • Avoid MEMORY.md files under the user's home directory because they are not part of the codebase. If multiple developers are working on the same project, this can lead to conflicts and inconsistencies.

Writing Feature Specs

Instead of starting with prompts, development started with specifications: feature requirements, acceptance criteria, test plans, and so on. I usually invest way more time in writing specifications than it takes to implement the feature. But it is a good investment, especially on large projects — like writing good documentation, it takes time, but it pays off in the long run.

The most important part of the development process is writing good feature specs from the beginning. Like instructions, they should be clear, concise, and contain all the necessary information for the agent to implement the feature. Try to cover edge cases, define acceptance criteria, and provide a test plan. It’s especially important to clearly define functional requirements, non-functional requirements, and acceptance criteria. If the spec is unclear, the agent will make too many assumptions, and the final result will be unpredictable.

Get Aleksandar Stojsavljevic’s stories in your inbox

Join Medium for free to get updates from this writer.

I found it useful to have a template for feature specs:

# Feature Title## Problem statement## Goal## Functional requirements### User/UX flow### API behavior## Non-functional requirements## Acceptance criteria## Test/Validation plan## Out of scope## Relevant files

The bare minimum is “Goal”, “Functional requirements”, and “Test/Validation plan”. The rest are optional. But in general, the more detailed the spec is, the better. Keep in mind that “detailed” != “verbose”.

Working with Different Agent Models

I mainly work with Sonnet 4.6 using medium effort. For bigger features, I use Opus 4.7 for planning. My two agents (code-reviewer and ui-tester) are configured to use Sonnet with different effort levels - high for code-reviewer and low for ui-tester.

I found Sonnet with medium effort to be the best balance between performance and quality. Opus is better for complex tasks, but it burns more tokens and can sometimes overengineer solutions. Haiku is fine for “donkey” work, but it does not follow instructions very well.

One positive trend I’m seeing lately is that Opus 4.7 has become much better at asking clarifying questions when the spec is unclear. This is especially important for planning. If the spec is not clear, I want the agent to ask questions instead of making assumptions. Still, I do my best to write clear specs from the beginning to minimize the need for clarifications.

Implementation Workflow

The implementation workflow looks like this:

## Planning
- [M] Create a plan (user calls `/plan`)
- [M] User reviews and accepts the plan
## Pre-Implementation
- [A] Review relevant documentation (overview, functional/non-functional requirements, architecture)
- [A] Create feature branch
## Implementation Phase
- [A] Implement the plan
- [A] Run relevant code validation commands (e.g. build, lint, test)
- [A] Run code-reviewer agent after changes (`@code-reviewer`)
- [A] If issues are found: fix -> code-reviewer re-review until clean
## 2nd Review
- [M] Run built-in review skill (user runs `/review local changes only`)
- [A] If issues are found -> Implementation Phase
- [M] Wait for user approval before continuing
## Pre-Commit Validation
- [A] Run validation script (local CI-like script)
- [A] If issues are found -> Implementation Phase
## UI Testing
- [A] Run ui-tester agent (`@ui-tester`) that uses chrome-devtools-mcp
- [A] Test the cases defined in the plan
- [A] If issues are found -> Implementation Phase
## Git Commit
- [A] Stage all changes
- [A] Commit using conventional commit format
- [A] Push to remote
## PR Creation
- [A] Generate PR summary in predefined format
- [A] Create PR
- [A] Add @copilot as reviewer
- [A] @copilot reviews the PR and leaves comments
- [M] Wait for GitHub Copilot to finish the review
## Addressing PR Review Comments
- [A] Retrieve all comments
- [A] Analyze every comment
- [A] Implement changes if justified -> Implementation Phase
- [A] Git Commit Phase
- [A] Reply individually to every @copilot comment
- [A] Post final summary comment tagging @copilot for re-review
- [A] @copilot re-reviews the PR
- [M] If issues are found -> Addressing PR Review Comments Phase
## Final Steps
- [M] Wait for @copilot final approval
- [M] Merge PR
- [M] Verify main branch deployment

[M] means a manual step, [A] means an agent step.

For small tasks, I skip some steps. For bigger features, I follow the full workflow.

Code Reviews

Because the entire codebase was AI-generated, I introduced strict CI-like static code validation: automated tests, coverage checks, linters, vulnerability scanning, CodeQL analysis, Trivy, and multiple review phases. The more autonomy the AI had, the more important it became to have good gatekeepers.

Having multiple agents is like having multiple developers reviewing the code. The formula is simple: the more reviewers, the better. To be fair, not all reviewers are equal, and too many reviewers can lead to noise and conflicting feedback. But in this case, I have a single coder that accepts or rejects feedback based on its judgment. So even if some feedback is not great, it won’t be accepted unless it makes sense.

I currently have 3 separate review phases:

  • My custom code-reviewer agent, which reviews code after every change. It uses a high-effort model. I usually perform most actions with a medium-effort model, but for code review, I want the agent to be as thorough and careful as possible.
  • Claude’s built-in /review skill. It is mainly designed for reviewing PRs, but it can also review local changes. I invoke it using /review local changes only. It's not as powerful as my custom reviewer, but it's fast and still catches issues.
  • GitHub Copilot reviews PRs directly on GitHub. I find it very useful. Its comments make sense in about 95% of cases and are rarely nitpicky. My Claude agent accepts them whenever they are justified.

One of the parts I like the most is watching different AI agents collaborate through pull requests. One model generates changes, another reviews them and points out problems. Then the coding agent analyzes the feedback and makes necessary adjustments. The entire workflow often looks like a human collaboration, and I would just supervise the process and jump in only when something looked suspicious.

Code Validation

Since the code is being generated by an AI agent, it’s extremely important to have a robust validation process in place. During the implementation phase, the agent only runs the necessary validation commands (e.g. build, lint, test). On one hand, that’s not enough to catch every issue. On the other hand, running the full validation suite after every single change is too time-consuming and counterproductive. So I decided to run the full validation suite only before committing the code.

I created CI-like validation scripts for both frontend and backend, which run several checks: build, test, lint, vulnerable dependency scan, and security audits for bad practices.

I also run GitHub validation workflows on every PR and every push to main. That gives me an additional validation layer with tools like CodeQL, Trivy, Snyk, Gitleaks, Hadolint, and Semgrep. The more validations I add, the better. The only downside is that they can take time, but since they run on GitHub, they do not block my work.

What’s Next: The New (and Boring) Reality of Software Engineering

After I set up the process, the excitement faded a bit. I got the sense that the magic was gone, replaced by boring reality — looking at the console, overseeing the process, pressing buttons here and there. And I’m afraid that might be the future of software development: writing specifications, overseeing the process, reviewing the code. But I’m bad at predicting the future, so chances are high that I’m wrong. Nobody knows what will happen a few years from now, not to mention a decade.

If you are curious about AI-assisted development, explore NetWatchly, send feedback, or suggest ideas. I am still experimenting and improving both the product and development workflows.

--

--