1. Introduction ✍️
It feels like ages ago, but the “first” release of ChatGPT by OpenAI was just a year and a half ago—crazy, isn’t it? 🤯 While some people still deny or try to avoid AI because they don’t yet see much value in it, I was actually one of the first, among my small group of developers, to embrace it. At the beginning, some of them actually laughed at me, but now all of them use some sort of AI daily 😂
I was super motivated since day one by how even the early versions of ChatGPT could help me out with repetitive tasks. Remember converting a PHP array to JavaScript manually? Crazy, isn’t it? Just paste it in GPT and ask! Remember wasting hours finding that missing comma that made your project break so hard? Those days are long gone! 🎉
So let’s deep dive into part of my workflow using AI—how I use it, how I approach it, and how I set up my tools. I’ll probably miss some topics, but I’ll try to keep this article up-to-date.
2. Tools 🛠️
It’s 2025, August, and we have a couple of tools available for us to use. There are a TON of them available, but honestly, I don’t have “time” yet to test them all. Some of them are also prototypes or not well-polished yet, so I’ll use whatever I feel is polished and saves my time! ⚡
I’ll focus on 3-4 important tools that I use daily: Cursor, Claude Code, Roo Code (Cline) & GPT.
2.1. Cursor 💻
Cursor was AMAZING when it was released, but the VC money needs to be paid at some point 💸. I can’t deny that I really ENJOYED the first days of Cursor, when it was still used by a small subset of people, where we actually had the chance to even debate prices with the CEO himself! As time went by and with the “Vibe Coding” hype that was going on Twitter, it was a matter of time before the price was raised, and they would start capping people who were abusing the free and paid tiers.
Cursor is my main editor at the moment, and I still believe it’s a good tool for the price that we pay, even with the 500 requests per month, but unfortunately, it doesn’t last me for the whole month if I use it daily.
Here’s what I love about Cursor:
- Autocomplete - For me, this is worth $10 alone. I was previously using Supermaven (now part of Cursor too) and was paying $10 per month for it, so yeah, the Tab + Autocomplete is just out of this world and there’s no competition so far. GitHub Copilot is nice, but Cursor tab is just nuts 🔥
- UI + Agent UI - This is really nice. While Claude Code is a nice agent, I love being able to simply drag files, tag files, see what we edited, accept changes, and so on. Some people are nerdy enough to say you could do everything in a terminal, yeah, but we’re in 2025—let’s use UIs too! They’re great and save us time 💫
- VSCode Ecosystem - Being able to use Cursor with VSCode extensions is just great. You still benefit from the HUGE VSCode ecosystem. No brainer for me 🧠
- Agent - Okay-ish. While there’s still a lot of room to improve, the Agent looks okay and does the job most of the time.
- Codebase Indexing + Docs - This is a really nice feature! You can index your codebase and your docs, so when you ask or delegate a task, it actually knows exactly how to search “files” and “docs” to find the best answer, making it way better instead of including all the codebase/files in the context.
What I don’t like about Cursor:
- The price - 500 requests that include tool calls and whatnot might be gone fast if you push it too much. Use your requests wisely! 💸
- No support (or partial support) for your own models/API keys. While this isn’t a big deal for everyone, I would love to see this happening at some point, but it’s not likely to happen soon since it’s their core business.
- A bit slow and laggy sometimes 🐌
- Wish there was better support for multiple mode switching and sub-tasks, etc., so we could use the context in a smarter way.
- Context cap - Cursor charges more for the “Max” model, which are basically the same models but with higher context.
I believe after the backlash from the community following the price raise, Cursor started to listen more to the community, and they’re actually trying to ship some nice features these days, so let’s keep an eye on this one 👀.
2.2. Claude Code 🧠
Well, well, this is the big boy as of today (time of this post). Anthropic really nailed it with Claude Code! I must admit, initially I didn’t like the idea of having it in the terminal and was actually expecting a VSCode extension, but I was wrong—this is actually a really nice tool! 🎯
Anthropic has been leading the AI coding models for a while now, and they did it again with Claude Code + Claude Sonnet. I would say most of the time it does the tasks that I ask perfectly, with very few exceptions.
Here’s what I love about Claude Code:
- Minimalist - Being in a terminal fits well. It can work with “any” IDE because it’s not an actual addon, so it just works—it’s fast and easy to use ⚡
- Can be installed anywhere you want. Need to code while traveling or at the coffee shop? You bet! Install Claude Code on your old MacBook or Raspberry Pi and you’re good to go! (I actually do this behind a Tailscale VPN) 🏖️
- Agent - I’m still not sure what they did here with Claude Code, but the agent is just so nice. I actually ran the same task with Sonnet 4 in different tools, and Claude Code did it better most of the time. They have some black magic behind it 🪄
- “Unlimited” - They claim unlimited for the max plan. If you do “fair” usage, you could probably use this all day long without being rate limited. Just make sure you edit your button colors yourself and don’t delegate it to the agent… PLEASE! 😅
What I don’t like about Claude Code:
- Nothing? - Nothing really. I’m not sure what to say here. Its just great!
- A UI please? - I would love to see a UI for Claude Code.
2.3. Roo Code 🦘
Roo Code (fork of Cline) is a VSCode Extension that’s getting more and more popular, and for good reason. It’s open-source, it doesn’t opine about the models, fully customizable, and it has a really nice agent as well! 🎨
Here’s what I love about Roo Code:
- Open-source - Yeah, you can actually contribute and help shape the future of Roo Code, fork it, or do whatever you want with it! 🔓
- Customizable - You can customize the agent rules, prompts, create new modes, and so on.
- Bring your own keys! - This is really nice! You can plug your OpenRouter keys, Anthropic keys, and even connect it to Claude Code!! 🔑
- Mode Switching - This is really a killer feature I would like to see in Cursor. You can basically Plan in the Architect mode, make it perfect, and then it will switch automatically to the Code mode and start executing the task. Here you have the chance to actually use nice models for the planning and then switch to a cheaper model for the actual coding—you could save some nice money here! 💰
- Tasks - Roo Code can also “divide” a bigger scope into multiple small tasks while still retaining part of the context of what it has done and what’s remaining to do. This is really nice and avoids polluting the context with too much stuff when you’re trying to implement a feature that contains a lot of things to do. It can fully go auto-pilot mode, do the small tasks, switch back, and continue where it left off with mostly zero effort! Really nice feature here! 🚗
- Codebase Indexing - Recently added, you can index your codebase with Qdrant. It will be like Cursor in this case.
- Cost Control - Since you bring your own keys, you can control the cost, how much you spend, etc.
What I don’t like about Roo Code:
- The UI still needs some work—it could be a bit more polished, but it’s getting better, and hey, it’s open-source, okay? We can contribute to it! 🛠️
- Laggy sometimes 🐌
2.4. OpenAI - GPT 🤖
While I don’t use OpenAI Codex or GPT often for coding or hard-core coding, I tend to use GPT in the browser to investigate quick things, quick questions, let it “web-search” for me topics that I’m curious about, compare choices like frameworks, libraries, and so on. I would say it’s my new “Google” 😄
2.5. Why not VSCode, Copilot, Devin, Windsurf, etc? 🤷♂️
Well, at some point you gotta pick what you’re comfortable with and stick with something. If you keep jumping between a zillion tools, you won’t be actually productive. So nothing against other tools—I’m sharing here what I use personally and what I think is the best fit for me.
I also created Days Since Last VSCode Fork if you’re curious about how long it’s been since the last time some VC-backed company forked VSCode 😂
2.6. Picking the right tool—how do I do it? 🎯
Well, usually I do the following:
- Claude Code - Actual features (important ones), bootstrap projects, and ideas. Bug finding, complex tasks.
- Cursor - Quick tasks in a file or two, trying to save up some requests, or when I need to tag a few files and I’m bored to actually do it on Claude Code.
- Roo Code - When I want a proper plan + execution and I want to explore some ideas while giving Claude Code some time to breathe.
- Often I could delegate different tasks (as long as they don’t touch the same files) to Claude Code + Cursor + Roo Code at the same time.
3. Prompting 📝
This is a very important topic! Most of the time, people are lazy enough to prompt. It’s known that if you don’t prompt correctly, you will probably also get bad results. LLMs are not magic—they need context, and you need to clearly explain what you want to achieve and what’s your goal with a specific task. Sometimes you need to give it a little “hint” to point it in the right direction, and that makes a lot of difference in the results! 🎯
Some people debate whether you waste more time prompting and battling the AI while you could do the code yourself—that’s correct! It happened to all of us. Sometimes you just wish you would have written the code yourself and you would probably spend less time, but I also think, how much time did I actually save? I would probably end up net positive! 😛
If you’re lazy, ensure that you have some prompts saved somewhere that you could easily copy and paste or get ideas from! 💡
Here are some examples of basic prompts I do often:
Ensure methods are single worded lower case if possible. If we are in "Browser", you dont need a method called "browserCleanup" -> cleanup() simple!!!
- You will be taking advantage of typescript return types and things that are infered by typescript without needing to define the return types unless its really needed.
- You will ensure best pratices for async patterns and promise patterns in JS, so avoiding awaits in for loops and prefer Promise.all()
- Avoid redundant Variables
- Ensure we dont do .push for example in async patterns that could lead to weird behaviour.
- Organize the types, avoid nesting types and extract smaller pieces like Arrays or Array of Objects into a new type to ensure we can read the types better.
- Dont create unecessary functions, unless we really need. Keep it inline whenever possible.
- Avoid more then 3 nested ?? null coalesce for example.
- Prefer ?? over ||
- Document methods with what they do, but dont write the params types on the signature. Keep it short.
- Remove any useless comments in the code that provide no value
- Avoid esle if and prefer early returns.
- If you need a lot of ifs, probably use ts-pattern package so we can use a "match"
- Lint and typecheck the file in question to ensure it gets passed the check tests
You will streamline this extractor, in a standard way so every other extract would follow the same patterns.
Here are some rules:
- Ensure if the map is complex, to see if we could streamline it
- attempt to avoid for loops, prefer promisses
- If always follow the same pattern for returning errors for example create a createError() for example.
- We want the following api clean!
- If needed you can create a file for each mapper, in this case create a folder next to the instagram, leaving the crawler a minimal "interface":
-- src/crawlers/api/github-crawler.ts
-- src/integrations/github/github-post.ts
-- src/integrations/github/github-profile.ts
With the way above, the code is really clean and each extractor as its own place.
That’s about it—you don’t need to write a full and super-duper detailed prompt, just the minimum effort to get the job done.
4. Project Rules 📋
Well, with 3 different tools and probably more to come during this year, and all of them having their own rules file (which is a bad idea, to be honest), we want to make sure that our project rules are the same for all of them at all times.
You probably already know what project rules are, but they basically define the “base” instructions that you want AI to follow in each conversation/agent/session. This will guarantee that you will get greater results, and AI will follow (hopefully 😅) these rules.
I really hope agents.md will be a thing in the future so we can stop this madness of having to create a rules file for each tool. If just .env
, vite.config.ts
, .eslintrc
, .prettierrc
, etc., are not enough—just 3 more files, bro! PLEASE! 😩
To overcome this (and because sometimes you don’t want to ask your teammates to add 30 files and folders to .gitignore
, or you’re scared they will fire you because you’re using AI tools), you can:
- Add a global gitignore that will ignore the
.roo
,CLAUDE.md
, and.cursor
for every project you touch. - Or you can convince them to use Ruler, a nice tool that will apply your rules to all other “tools” that you might want. So you have a single file that is kept in “sync” with all other tools.
Here’s my config for Ruler:
# .ruler/ruler.toml
[agents.claude]
enabled = true
output_path = "CLAUDE.md"
[agents.cursor]
enabled = true
output_path = ".cursor/rules/global.mdc"
[agents.cline]
enabled = true
output_path = ".roo/rules/global.md"
---
description: My Monorepo - Project Structure & Rules
globs: **/*
alwaysApply: true
---
# My Monorepo - Project Structure & Rules 📑
## Overview
A Turbo monorepo for a web app to save and search stuff from various sources using AI
Then on your package.json you can add the following command to apply the rules:
"scripts": {
"ruler": "ruler apply --agents claude,cursor,cline"
}
That’s it! When you change your base instructions, you can just run bun ruler
and it will apply the rules to all other tools.
4.1. Generating Project Rules ⚙️
This is probably “basic” for most people (probably). You should use /claude init
or ask Cursor to generate the rules for you, BUT never forget to give it a look and tweak it to your own taste and needs. Sometimes these rules are quite generic and contain a lot of stuff that is probably useless for your project. Tokens are not free, so you should be smart about it! 💸
Here’s a small example of what I usually do:
Psst. This is a secret project I’m releasing soon! 🤫
---
description: Bookmarks Monorepo - Project Structure & Rules
globs: **/*
alwaysApply: true
---
# Bookmarks Monorepo - Project Structure & Rules 📑
## Overview
A Turbo monorepo for a web app to save and search bookmarks from various sources (Twitter, GitHub, etc.) using AI
## Monorepo Structure
- **Package Manager**: Bun (v1.2.20)
- **Build System**: Turbo + Vite (using rolldown-vite)
- **Frontend Framework**: React 19 + TanStack Router/Start
- **Database**: PostgreSQL + Drizzle ORM
- **Styling**: Tailwind CSS v4 + Shadcn UI
- **Search**: Meilisearch
- **Background Jobs**: Trigger.dev
- **Real-time**: Pusher/Laravel Echo (WebSockets)
- **AI**: Vercel AI SDK, OpenAI
- **Authentication**: Better Auth
- **Storage**: S3/R2 compatible storage
### Root Level
- `turbo.json` - Turbo pipeline configuration with task dependencies
- `package.json` - Root workspace with Turbo scripts and version overrides
- `bun.lock` - Bun lockfile for dependencies
### Applications (`apps/`)
- `apps/web/` - Main bookmarks web application (React + TanStack Start)
- `apps/browser-extension/` - Browser extension for bookmarks (active development)
### Shared Packages (`packages/`)
- `packages/ai/` - AI utilities and prompt building
- `packages/attempt/` - Error handling utilities with tryCatch patterns
- `packages/cli/` - Command line interface utilities
- `packages/crawler/` - Web crawling functionality
- `packages/database/` - Drizzle ORM schema and migrations
- `packages/env/` - Environment variable validation
- `packages/eslint-config/` - Shared ESLint configuration with custom rules
- `packages/indexer/` - Meilisearch indexing utilities
- `packages/integrations/` - External service integrations (GitHub, Twitter)
- `packages/logger/` - Logging utilities
- `packages/monitoring/` - Performance and error monitoring
- `packages/sockets/` - WebSocket authentication and types
- `packages/storage/` - File storage abstractions (S3, R2, local)
- `packages/types/` - Shared TypeScript types and Zod schemas
- `packages/typescript-config/` - Shared TypeScript configurations (base + react)
- `packages/ui/` - Shadcn UI components and hooks
- `packages/utils/` - General utility functions
## Web App Structure (`apps/web/src/`)
src/
├── components/ # React UI components
├── console.ts # CLI commands entry point
├── css/ # Tailwind CSS styles
├── entry-points/ # App entry points (client, server, router)
├── hooks/ # React hooks
├── jobs/ # Trigger.dev background jobs
├── lib/ # Utilities and configurations
├── prompts/ # AI prompt templates
├── routes/ # TanStack Router route components
├── routes.ts # Route configuration
└── services/ # Business logic and API layer
## Tech Stack
- **Frontend**: React 19 + TanStack Router/Start
- **Database**: Drizzle + Postgres
- **Styling**: Tailwind CSS v4 + Shadcn
- **Package Manager**: Bun
- **AI**: Vercel AI SDK, Meilisearch
- **Build System**: Turbo + Vite
- **Jobs**: Trigger.dev
- **Architecture**: Laravel inspired MVC
## Key Commands
- `bun run build` - Build all packages
- `bun run dev` - Run main app in development
- `bun run lint:fix` - Lint all packages with shared config
- `bun run typecheck` - Run typescript Checks
- `bun lint:fix && bun typecheck`- Run both
## Package Dependencies
- Apps use workspace packages via `workspace:*` protocol
- TypeScript version consistency enforced via root overrides
- ESLint configuration shared across all packages
- Caching handled by Turbo in `.turbo/` folder
## Development Rules
### TypeScript & Types
- **Zod Types**: Use `z.infer<typeof Schema>` for Zod-derived types
- **Imports**: Use `import * as` for namespace imports (services, Radix/Shadcn)
- **Type Safety**: Prefer type inference, interfaces over types, no TS enums
- **Coercion**: Use `z.coerce.number()` for ID fields that may come as strings
- **Return Types**: Let TypeScript infer return types when possible. Only add explicit return types when:
- External APIs return `any` (chrome/browser APIs, fetch responses)
- Type inference breaks due to complex logic
- Public API methods that need documented return types
- **Type Organization**: Extract shared types to dedicated files in `src/types/` folder
- **No ANY**: Never use `any` type except when dealing with external API responses where typing is impossible
### Code Organization
- **Services**: Export plain functions, use classes sparingly
- **Actions**: TanStack Server Functions with `attempt.try()` error handling
- **Components**: Extend component props with `ComponentProps<typeof Component>`
- **File Naming**: kebab-case for files/directories
- **Method Naming**: Use single-word method names when possible (e.g., `sync()` instead of `syncBookmarks()`)
- **Business Logic**: Keep business logic out of React components - use service classes/functions
- **State Management**: Centralize state operations within service methods, not in components
- **Utilities**: Prefer static methods within service classes over separate utility files
### Error Handling
- **Result Pattern**: Use `attempt.try()` for [error,data] patterns instead of try/catch
- **Client Errors**: Set `client: true` in attempt options for user-facing errors
- **Custom Errors**: Use `ErrorException('ERROR_CODE')` for known error types
- **Error Mapping**: Provide error mappers in attempt options
### Code Style
- **Early Returns**: Always prefer early returns over nested conditions
- **No Nested Ternaries**: Keep conditionals readable
- **Destructuring**: Destructure props and options at function start
- **Nullish Coalescing**: Use `??` over `||` for nullable values
- **Pattern Matching**: Use `ts-pattern` for complex conditionals
- **React Conditionals**: Use ternary operators (`condition ? <Component /> : null`) instead of `&&` pattern to avoid leaked values
- **Comments**: Remove useless comments, keep code self-documenting
- **Function Organization**: Remove unused methods, inline browser API calls when single-use
- **Generic Functions**: Use generic types with proper constraints for reusable utilities (e.g., `sort<T extends Bookmark>(bookmarks: T[])`)
- **No Comments**: Do not add comments unless explicitly requested by the user
### Database
- **Query Builder**: Use Drizzle ORM with `db.query` syntax
- **Transactions**: Support optional `tx` parameter for transaction support
- **Relations**: Use `with` clause for eager loading related data
- **Validation**: Always question new columns, prefer defaults over null-checking
### Performance
- **Parallel Processing**: Use `Promise.all()` for concurrent operations
- **Batching**: Batch database queries and API calls when possible
- **Caching**: Cache expensive operations with appropriate TTL
### Package Manager
- **Bun**: Use `bun` for all package management tasks
### Key Dependencies
- **Zod v4**: All validation (`import { z } from 'zod/v4'`)
- **TanStack**: Router, Query, Start for full-stack patterns
- **Drizzle ORM**: Database with type safety (`db.query` syntax)
- **ts-pattern**: Functional pattern matching with `match()`
- **react-hook-form**: Form management with Zod validation
- **Motion**: Animations (`motion/react`)
- **Radix UI**: Headless components via `radix-ui`
- **CVA**: Component styling variants
### Database Schema Workflow
- **Tables**: Define in `packages/database/src/schema.ts`
- **Schemas**: Mirror in `packages/types/src/database.schema.ts` with Zod
- **Sync**: Keep database and Zod schemas synchronized
- **Relations**: Export tables and relations separately
### Tasks & Jobs
- Interate on lints, first do everything then on the end run `bun lint:fix && bun typecheck`
- Dont attempt to run tests or anything unless asked, ask me to test and ill do it and paste the output
- Once your are done with a task, please also give a double "check" to make sure everything is done and working as expected
### Development Server & Debugging
🚨 **CRITICAL**: **NEVER** run `bun run build` or `bun run dev` commands when debugging or pair programming! This interferes with the user's development workflow.
**Rules for Development Server:**
- **User controls the dev server** - Always ask the user to start/stop development servers
- **Never build for production** during debugging - it creates cached files that break HMR
- **Ask for logs** - Request console output, error logs, or debugging information from the user
- **Let user test** - Ask user to test functionality and paste results/logs
- **HMR workflows** - Respect that users may be running Hot Module Reload and your builds can break their workflow
**When debugging browser extensions:**
- Ask user to reload extension in browser after code changes
- Request specific console logs from extension devtools vs browser console
- Never run build commands - let the user's dev server handle file updates
**Correct debugging approach:**
❌ bun run build
❌ bun run dev
✅ "Can you start the dev server with bun run dev?"
✅ "Please reload the extension and paste the console logs"
✅ "Can you test this and share what logs you see?"
#### Naming Conventions
- **Files/Directories**: kebab-case (e.g., `user-service.ts`)
- **Classes**: PascalCase (e.g., `UserService`)
- **Variables/Methods**: camelCase (e.g., `getUserById`)
- **Database Tables**: snake_case (e.g., `user_bookmarks`)
- **Database Columns**: camelCase with snake_case for timestamps (e.g., `userId`, `created_at`)
- **Service Methods**: Single-word names when possible (e.g., `sync()`, `get()`, `set()`, `clear()`)
- **Integration Methods**: Domain validation (`isValid()`), data operations (`sync()`, `fetch()`, `parse()`)
- **Avoid Verbose Names**: Prefer `twitter()` over `syncTwitterBookmarks()`, `browser()` over `syncBrowserBookmarks()`
5. Controlling the Output and Code Quality 🎨
Well, as we’ve seen before, we can do a lot to make our output better, instruct AI better, and try to save time. While this could look like it’s really easy to get on the right track, after you’re done with a setup that you like, you’ll probably just copy-paste or tweak to other projects or tasks that you’re doing daily.
BUT, while AI can do a lot of code, it’s important to keep monitoring the output and code quality. We are not “vibe coding” here—we’re actually trying to get AI to do creative work, help us save time, and unblock real-world problems. If you’re not getting the results you want, try switching your prompt, the way you ask, or the way you instruct AI to do something. That’s often the problem. It could also happen that AI models are being nerfed, and you should just go touch grass 😛 or actually do code, because that’s what you’re paid for? lol 😂
6. What you should not do 🚫
- Small Tasks - If you want to change a color or a font-size, please just do it yourself, or at dont cry when your Cursor/Claude requests are gone, if you did you deserve to pay for it:p
- Know when when to stop session/chat - There is limited context, so if you feeling like your doing too much on a session/chat, try to start a fresh one!
- Stuck in Endless Loop - If you feel like AI is not giving you the results you want, dont get mad! Breath a bit, start a new chat and try to approach it in a different way, explain different things, give more context.
- Dont stop being a programmer - You are programmer, so please be creative, while its nice to get AI by your side, dont forget to be creative, understand what your code is doing, what it should do, and how you want to shape it, you are in control!
6. Conclusion 🎉
Well, that’s it for now! Just wanted to brain dump some thoughts and ideas, and workflows that I use daily. Things are changing, so it’s highly likely that this will be outdated in a few months, but I hope you enjoyed this article and that you’ll find it useful.
OH, this article was NOT written by AI btw lol! I just fixed typos sorry for that!
If you’re on X or Bluesky make sure to give me a follow! @nikuscs or @nikus 🐦