9 May 2025 Hassan Syed

AI Dev with Cline/Cursor - Efficient Context Management Is The Key

I’ve now spent a good amount of time working with Generative AI-powered automated coding and testing, experimenting with multiple tools. For small, focused tasks within a single file or component, both Cline and Cursor p

Author

Hassan Syed

AI Architect | Generative AI SME | Azure Certified Solution Expert | Enterprise Systems | IoT Solutions | Big Data | Digital Transformation Leader | Integration Architect | Hands-on| Mentor

Helping organisations turn AI ambition into secure systems, confident teams, and measurable value.

Hassan Syed is an enterprise AI architect, founder, transformation coach, teacher, and writer with more than 20 years of experience designing and delivering complex technology systems.

Follow on LinkedIn

I’ve now spent a good amount of time working with Generative AI-powered automated coding and testing, experimenting with multiple tools. For small, focused tasks within a single file or component, both Cline and Cursor performed admirably—suggesting code completions, assisting with refactoring, and even generating entire functions based on comments.

However, as I began to leverage these tools for more complex, cross-codebase tasks, I ran into some unexpected challenges. It became clear that—tools aside—we need fresh approaches to modularising code to make it more manageable for AI, largely due to the context window limitations that large language models (LLMs) still face.

To put it simply: humans retain knowledge as we stay engaged with a task; LLMs are stateless, requiring context to be re-supplied every time we interact with them.

This is a story of my ongoing experiment—an experience I believe is worth sharing. We are undeniably entering a new era of software development: the age of AI Engineering.

Note: It is too early to say this will be the ultimate solution, I am pretty sure we will see a lot of movements, giving rise to new patterns and architecture approaches. The Context Window Problem

Despite our modular architecture, each of our three main components had grown substantially. The frontend had ballooned to thousands of lines across dozens of files. The backend and microservices code weren’t far behind either.

When I asked Cline to help with a feature that touched both the frontend and backend, I discovered its context window limitations. It would make suggestions for the frontend code without awareness of critical backend constraints, or vice versa. Even within a single tier, it struggled to maintain context across multiple interconnected files.

Four Key Challenges with AI Tools in My Large Codebase

  1. Context Window Constraints

Despite Cline’s attempts to handle large contexts, it frequently missed important functions or produced incomplete code when dealing with my larger files. Some of my React components had grown to several thousand lines with complex state management logic. Cline would sometimes suggest modifications that referenced component methods it hadn’t “seen” because they were outside its context window.

  1. Cross-Component Confusion

When implementing features that spanned my three-tiered architecture, both tools struggled to maintain a coherent understanding of the entire feature flow. For example, when enhancing my feedback simulation feature, Cursor would make backend changes without understanding how they affected the microservice implementation, or vice versa.

  1. Performance Degradation

As I fed more context into these tools to help them understand my codebase better, their performance suffered dramatically. Response times slowed from nearly instant to several seconds—sometimes even timing out completely. This severely hampered the “flow state” my developers valued.

  1. Rising Costs

With Cline’s token-based pricing model, my costs began to escalate rapidly. As I fed larger and larger chunks of my codebase to get better context-aware suggestions, I watched my costs increasing rapidly. And often I had to discard my entire branch of new code, completely wasting the money spent on the LLM APIs.

It became clear that despite my initial modular architecture, I needed to refine my approach if I wanted to effectively leverage these AI coding assistants.

So, in summary the issues I faced:

Limited context window – AI couldn’t “see” all relevant code. Cross-layer confusion – Changes didn’t align across tiers. Slow response times – Too much code made the tools sluggish. High costs – More code = more tokens = higher bills.

The Solution: Micro-Componentisation with a Frontend Shell

Rather than abandoning these promising AI tools, I decided to experiment with further decomposing my architecture. Instead of three large tiers, I would create smaller, more focused micro-components that aligned better with the AI tools’ context limitations.

My hypothesis was simple: if I restructured my codebase into smaller, more cohesive units, I could feed just the relevant pieces to the AI assistants, improving their effectiveness without overwhelming their context windows

Step 1: Feature-Based Restructuring

My first major change was shifting from a technology-based structure to a feature-based structure. Instead of organising code by technical layer (frontend/backend/microservices), I reorganised around product features:

Note: Some fake names have been used in this article and the diagram below.

This reorganisation meant each feature directory contained all the code needed to implement that feature across all tiers. A developer (or AI assistant) working on the learning path feature could now focus on just that directory, which contained all relevant frontend, backend, and microservice cod

The Frontend Shell Approach

While breaking down my code by feature was a good start, I still needed a way to create a cohesive user experience. I couldn’t simply have disconnected feature frontends—users expect a unified application. This led me to implement a frontend shell architecture.

The frontend shell serves as a container that dynamically loads and integrates the individual feature frontend components. It handles:

Centralised routing and navigation between features Common UI elements like headers, footers, and navigation menus Authentication and authorisation Global state management Loading and bootstrapping feature modules

I implemented this shell using module federation, a webpack 5 feature that allows loading remote modules at runtime. This meant each feature frontend could be developed, tested, and even deployed independently, but would integrate seamlessly into the main application shell.

// Example module federation configuration in webpack.config.js module.exports = { // …other webpack configuration plugins: [ new ModuleFederationPlugin({ name: ‘app_shell’, filename: ‘remoteEntry.js’, remotes: { learningPath: ‘learning_path@http://localhost:3001/remoteEntry.js’, feedbackSim: ‘feedback_sim@http://localhost:3002/remoteEntry.js’, userManagement: ‘user_management@http://localhost:3003/remoteEntry.js’, }, shared: { react: { singleton: true, eager: true }, ‘react-dom’: { singleton: true, eager: true }, // other shared dependencies }, }), ], };

This approach gave me the best of both worlds: modular, focused codebases that worked well with AI tools, but a seamless, integrated experience for end users.

Step 2: Strict API Contracts

To maintain system integrity despite this decomposition, I implemented stricter API contracts between components. I used OpenAPI/Swagger for REST APIs and Protocol Buffers for service-to-service communication. These contracts served as clear boundaries that both humans and AI tools could understand.

learning-path/api/schema.yaml

openapi: 3.0.0 paths: /api/learning-path/{userId}: get: summary: Retrieve a user’s learning path parameters: - name: userId in: path required: true schema: type: string responses: 200: description: A learning path object content: application/json: schema: $ref: ’#/components/schemas/LearningPath’

Step 3: Bounded Context Implementation

Taking inspiration from Domain-Driven Design, I established clear bounded contexts for each feature. This meant ensuring that each feature module was as self-contained as possible, with minimal dependencies on other features. When dependencies were necessary, they were exclusively through the defined API contracts.

This approach created natural boundaries that aligned perfectly with the context windows of my AI tools. When working on the feedback simulation feature, I could provide Cline or Cursor with just the feedback simulation feature code—a much smaller and more cohesive set of files that fit comfortably within their context limitations.

Working with the Shell in AI Tools

When working with AI coding tools, I adopted a strategic approach to the frontend shell:

For feature-specific work, I would only provide the relevant feature module’s context to the AI. For example, when enhancing the learning path UI components, I’d only feed the learning path frontend code to Cline or Cursor.

For shell-specific work (like adding a new global navigation item), I would provide only the shell code, along with the minimal feature interface information needed.

When I needed to integrate a feature into the shell, I would create specific prompts that included just the relevant shell integration points and the feature’s integration code, rather than the entire codebase.

This approach dramatically improved the AI’s ability to provide useful suggestions while keeping token usage (and therefore costs) under control.

In summary:

Break It Down by Feature

Reorganised the project by feature, not by technology. Each feature became a self-contained unit with:

Frontend components API logic AI service backend

Frontend Shell: The Glue Layer

To keep a unified UI experience, I built a frontend shell that:

Handles navigation, auth, and layout Loads each feature frontend dynamically (using Webpack Module Federation) Keeps the user experience seamless

Strict API Contracts + Bounded Contexts

To reduce confusion and protect integrations:

I used OpenAPI for REST contracts Protocol Buffers for internal services Each feature adhered to a bounded context (inspired by DDD)

This made it easier for both humans and AI to reason about the system.

Working with AI Tools, Smarter

Instead of feeding entire codebases into Cline or Cursor, I gave them:

Only the code for one feature at a time Only the frontend shell when making global UI changes Targeted prompts with focused context

The Results: Transformative Improvements

The impact of this restructuring on my AI-assisted development was immediate and dramatic:

  1. More Accurate and Contextual Suggestions

By focusing the AI tools on smaller, more cohesive feature modules, their suggestions became remarkably more accurate. When working on the learning path generator, Cline now had visibility into all relevant code across frontend, backend, and service tiers—but only for that specific feature. This focused context enabled it to make suggestions that respected my patterns and maintained consistency across the feature’s implementation.

  1. Faster Response Times

With smaller chunks of code to process, both tools became noticeably more responsive. Cursor’s suggestions once again appeared almost instantaneously, and Cline’s processing time dropped from several seconds to under a second in most cases. This restored the flow state my developers valued.

  1. Drastically Reduced Costs

My Cline usage costs decreased by approximately 70% despite increased overall usage. By feeding it smaller, more relevant portions of my codebase, I significantly reduced my token consumption while actually improving the quality of suggestions.

  1. Fewer Integration Issues

The clearer boundaries between features, enforced through strict API contracts, meant that AI-suggested changes were less likely to cause unintended side effects. When the AI did propose changes that would violate a contract, my automated testing caught these issues immediately.

  1. Improved Team Autonomy

An unexpected benefit of this architecture was increased team autonomy. With clear feature boundaries, individual teams could work more independently on their assigned features, leveraging AI assistance without risking conflicts with other teams’ work.

Key Insights for Fellow Architects

Through this journey of refining my architecture to work better with AI coding tools, I’ve developed several principles that may help other teams facing similar challenges:

Organise by Feature, Not Layer – Easier for AI and developers alike Use API Contracts – Clear boundaries protect your system Balance Independence with Integration – Frontend shell helps Test Contracts, Not Implementations – Let AI innovate safely Keep Components Small Enough for AI to Understand

Conclusion: A New Symbiosis

What I found most fascinating about this journey is that the architectural changes I made to accommodate AI coding assistants ended up improving my codebase for human developers as well. The smaller, more focused feature modules made it easier for new team members to get up to speed. The strict API contracts reduced unexpected side effects from changes. The clearer separation of concerns made reasoning about the system simpler.

The frontend shell approach proved to be the perfect complement to the micro-componentisation strategy—providing a cohesive user experience while still allowing for focused, AI-friendly development of individual features.

This experience has convinced me that we’re entering a new era of software architecture—one where we must design systems not just for human comprehension but also for effective AI assistance. The good news is that these goals are largely aligned. Code that is well-structured, modular, and has clear boundaries is easier for both humans and AI tools to understand and modify.

As AI coding tools continue to evolve, I expect we’ll see further refinements in how we structure our codebases to maximise their effectiveness. But the fundamental principles I discovered—feature-based organisation, strict contracts, appropriate component sizing, and balanced integration through approaches like the frontend shell—will likely remain relevant regardless of how the tools themselves advance.

For teams working with large codebases and looking to leverage AI coding assistants, my advice is clear: don’t fight the tools’ limitations—adapt your architecture to work with them. The benefits in terms of development velocity, code quality, and team productivity are well worth the effort.

This journey has transformed how I think about software architecture in the age of AI assistance. I hope my experience helps you navigate your own path toward a more effective symbiosis between human developers and their increasingly capable AI collaborators.

Originally published via LinkedIn. View source ↗
← Back to Articles