Over the past three years, I've integrated AI tools into almost every aspect of my development workflow. From migrating legacy bash scripts to TypeScript CLIs, building Next.js applications, writing backend and frontend TypeScript code, creating Helm charts, managing infrastructure with Terraform, to debugging code across multiple languages - AI has become an essential part of how I work. I've used it for everything from improving complex SQL queries to writing Python scripts, and even for non-coding tasks like drafting postmortems and technical reviews. Here's what I've learned along the way about making AI tools truly useful in real-world development.
I primarily use two AI tools for coding:
-
Claude Pro: My main tool for complex coding tasks and project work. While there are many AI coding assistants available, I've found Claude to be the most reliable and capable of understanding complex contexts without veering off course. The investment in the pro version has paid off in terms of consistency and capabilities.
-
GitHub Copilot: My go-to tool while actively coding. It serves two primary purposes in my workflow:
- As an intelligent autocomplete tool that significantly speeds up my coding process
- For quick fixes to code issues or minor errors in Claude-generated code
- When I'm feeling lazy, I can write a comment describing what I want, and Copilot will generate the code inline
This combination of tools provides a powerful workflow where Claude handles the heavy lifting of complex tasks, while Copilot helps with the minute-to-minute coding process.
One of the most important aspects of my workflow is how I position myself when working with AI. Rather than simply copying and pasting code, I approach the relationship in two key roles:
-
Product Owner: I clearly define requirements, constraints, and expectations. The AI serves as the developer implementing these requirements.
-
Code Reviewer: This is perhaps the most crucial role. Just like with human developers, code review makes all the difference. I carefully review AI-generated code for:
- Package choices and dependencies
- Redundant or unnecessary code
- Performance implications
- Security considerations
- Edge cases and error handling
- Consistency with existing codebase
- Best practices and patterns
This approach ensures that while AI accelerates development, we maintain high code quality and thoughtful architecture decisions.
One of the most crucial aspects of working with AI is providing the right context. I use two main commands for this purpose:
tree --gitignore | pbcopy
This command creates a tree representation of your project while respecting .gitignore rules. Here's how to customize it:
- Remove
--gitignore
if you want to see all files, including ignored ones - In macos
pbcopy
copies the output directly to your clipboard.
find . -type f -not -path '*/\.*' -not -path '*/public/*' -print0 2>/dev/null | \
grep -vzZ -- '-lock\.yaml$' | \
xargs -0 sh -c 'for f do git check-ignore -q "$f" || (echo "=== $f:" && cat "$f"); done' sh | pbcopy
This command compiles all your relevant code files into a single text output, making it easy to share with AI. It:
- Finds all files in your project
- Outputs each file's content with its filename and path as a header (this is really important for AI to understand the context)
- Excludes hidden files, public directories, and lock files
- Checks each file against git ignore rules
- Copies everything to your clipboard
Here's how to modify it:
-
find . -type f
: Finds all files in the current directory and subdirectories- You can specify a different path instead of
.
to search in a specific directory - Add
-maxdepth N
to limit directory depth
- You can specify a different path instead of
-
-not -path
patterns: Excludes specific paths- Add more patterns with additional
-not -path
arguments - Common patterns to exclude:
node_modules
,dist
,build
- Add more patterns with additional
-
grep -vzZ -- '-lock\.yaml$'
: Excludes lock files- Modify the pattern to exclude different file types
- Add multiple patterns with
\|
The command is particularly useful in two scenarios:
- When iterating on existing code
- When creating new features that should maintain consistency with existing patterns
However, there's an important caveat: be mindful of context window limitations. For instance, when working on a new page in a Next.js website, you don't need to share the entire codebase. Instead, focus on sharing similar components or pages that can serve as style and architecture references.
AI has become an invaluable brainstorming partner. Here's how I leverage it:
When facing complex tasks, I start by:
- Writing out my initial thoughts
- Listing potential challenges and issues
- Identifying possible tools and approaches
- Using AI to discuss pros and cons of different approaches
- Exploring edge cases and potential pitfalls
AI is excellent for brainstorming performance improvements. I can discuss:
- Different optimization strategies
- Tradeoffs between approaches
- Potential bottlenecks
- Scalability considerations
A key lesson I've learned is that AI works best with incremental development. Here's my approach:
-
Start with Structure: When beginning a new project, I first ask the AI to create a shell script that will generate the project structure and files. This gives us a solid foundation to build upon.
-
File-by-File Development: Rather than trying to generate an entire application at once, I work on one file at a time. This approach leads to better quality code and fewer errors.
-
Fresh Conversations: I start new conversations when the context gets too heavy. Long conversations can lead to degraded results, so I regularly begin fresh with updated context.
The real power of AI is in how it helps catch and solve problems early while maintaining rapid development speed. Here's a concrete example:
Recently, I needed to add an email service library to a monorepo that would be used across different parts of the application:
- Website reporting features
- Cron jobs
- Various other services
The constraint was simple: AWS SES limited us to 14 emails per second. Instead of building a complex solution from scratch, I shared my existing email sending code with the AI and asked it to incorporate rate limiting. The AI quickly implemented a token bucket algorithm and suggested future improvements like using Redis for distributed rate limiting, adding retry mechanisms, and implementing queue systems for high-load scenarios.
This is a perfect example of how AI accelerates development:
- Quick implementation of immediate needs
- Thoughtful suggestions for future scaling
- Early identification of potential issues
Understanding AI's limitations is crucial for effective collaboration. For instance, Claude typically can generate around 400 lines of code at a time. When you need more, a simple "Continue writing" prompt usually works. If it loses context, don't hesitate to go back and edit your message or try again.
While this guide focuses on coding workflows, it's worth noting that AI can enhance many other aspects of software development. I use it for various tasks like:
- Writing postmortems based on incident conversations
- Analyzing performance bottlenecks
- Reviewing configuration changes
- Documenting complex systems
The possibilities are vast, and I encourage you to experiment and discover how AI can best serve your specific needs and workflow. The examples I've shared are just a starting point – the real power comes from finding your own ways to leverage these tools effectively.
One of the biggest advantages of coding with AI is how it helps identify and solve problems early in the development cycle. You can:
- Quickly prototype different approaches
- Test edge cases and limitations
- Explore scaling considerations
- Implement best practices from the start
Instead of building something basic and iterating later, AI helps you think through and implement robust solutions from the beginning, while still moving at incredible speed.
- Tool Selection: Use different AI tools for different purposes - Claude for complex tasks, Copilot for active coding.
- Smart Context: Share context intelligently, focusing on what's relevant rather than dumping entire codebases.
- Incremental Development: Break down development into smaller, manageable chunks.
- Regular Refresh: Don't hesitate to start fresh conversations when needed.
- Understand Limitations: Work within the AI's capabilities rather than fighting against them.
- Hybrid Approach: Combine AI assistance with traditional coding for the best results.
- Review Mindset: Always review AI-generated code with the same rigor you'd apply to human-written code.
- Brainstorming Partner: Use AI for planning and problem-solving, not just code generation.
- Early Optimization: Leverage AI to implement robust solutions from the start.
By following these practices, I've found that AI becomes a powerful ally in development rather than a source of frustration. The key is to treat it as a sophisticated tool that requires proper handling rather than a magical solution that will write your entire application in one go.
Remember, the goal isn't to replace traditional coding practices but to enhance them. When used correctly, AI can significantly speed up development while maintaining code quality and consistency.