nyxcore-systems
7 min read

Unleashing AI on Code: Our Journey to a Full-Stack Code Analysis Extension

Dive into the development journey of building an AI-powered code analysis extension, from database schema to a real-time dashboard, tackling challenges and leveraging modern web technologies.

AICodeAnalysisDevelopmentTypeScriptPrismaLLMNext.jsSSEDXFullStack

The world of software development is constantly evolving, and keeping a firm grasp on code quality, architecture, and documentation across large, dynamic repositories can be a monumental task. What if AI could lend a hand, not just generating code, but understanding it, identifying patterns, and even documenting it automatically?

That was the ambitious goal behind our latest feature: a comprehensive, AI-powered Code Analysis extension. After an intense, focused development session, I'm thrilled to announce that the core functionality is now feature-complete, from the deepest database schema to a sleek, real-time dashboard UI. This post walks through the exciting seven-phase journey of bringing this vision to life.

The Vision: A Seven-Phase AI Code Auditor

Our aim was to build an end-to-end system that could:

  1. Ingest code from repositories.
  2. Analyze it for predefined and semantic patterns.
  3. Generate various forms of documentation.
  4. Present these insights in an intuitive, interactive user interface.

This required a tightly integrated system, touching every layer of our application stack. Let's break down how we built it.

Phase 1: Laying the Data Foundation with Prisma and RLS

Every powerful application starts with a robust data model. For our code analysis feature, we needed to store information about repositories, individual files, analysis runs, detected patterns, and generated documentation.

We extended our prisma/schema.prisma with five new models: Repository, RepositoryFile, CodeAnalysisRun, CodePattern, and GeneratedDoc. We also updated existing User and Tenant models to reflect these new relationships. Crucially, we implemented Row-Level Security (RLS) policies in prisma/rls.sql for all new tables, ensuring data isolation and security from the ground up. A quick npx prisma db push && npx prisma generate brought our database and client into sync.

Phase 2: The Scanner and File Indexer – Bringing Code to Life

With the database ready, the next step was to get the actual code into our system. This is where the src/server/services/code-analysis/scanner.ts and src/server/services/code-analysis/file-indexer.ts came into play.

The scanner.ts acts as our repository explorer, using an AsyncGenerator to stream ScanEvents as it traverses a GitHub repository tree, fetching file content via our github-connector.ts. This ensures efficient processing of even large repositories. The file-indexer.ts then takes these raw files, performs language detection (based on file extensions), extracts crucial metadata, and categorizes them, preparing the data for deeper analysis.

Phase 3: Doc Generator – AI's Pen to Paper

One of the most exciting aspects is the AI's ability to generate documentation. Our src/server/services/code-analysis/doc-generator.ts is another AsyncGenerator, designed to produce five types of documentation: READMEs, API docs, architecture overviews, onboarding guides, and changelogs.

This service leverages our resolveProvider() mechanism for "Bring Your Own Key" (BYOK) LLM calls, allowing users to integrate their preferred large language models. A key innovation here is scoreDocQuality(), a heuristic scoring function that evaluates the generated documentation, giving us a quantitative measure of AI output quality.

Phase 4: Pattern Detector – Unearthing Code Insights

Identifying recurring patterns, both good and bad, is at the heart of code analysis. The src/server/services/code-analysis/pattern-detector.ts is a sophisticated AsyncGenerator that processes files in batches (around 50k characters per batch) for LLM semantic analysis.

It parses the LLM's JSON responses for patterns (parsePatternResponse) and intelligently deduplicates findings across batches (deduplicatePatterns). We defined eight crucial pattern types: architecture, naming conventions, error-handling, testing practices, dependency management, security vulnerabilities, performance bottlenecks, and coding style.

Phase 5: Orchestration and Real-time Feedback with SSE

Connecting all these asynchronous generators and services required a robust orchestration layer and a way to provide real-time updates to the user.

  • src/server/services/code-analysis/analysis-runner.ts orchestrates the entire pipeline: scan → patterns → docs.
  • src/server/trpc/routers/code-analysis.ts serves as our main tRPC router, with sub-routers for runs, patterns, and docs. It uses protectedProcedure and llmProtectedProcedure for secure API access.
  • For real-time updates, we implemented an SSE (Server-Sent Events) endpoint at src/app/api/v1/events/code-analysis/[id]/route.ts, allowing the UI to stream analysis progress and results as they happen. Finally, codeAnalysisRouter was registered in src/server/trpc/router.ts to make it accessible across the application.

Phase 6: The Dashboard UI – User's Window into Code

What's powerful analysis without a compelling user interface? We built a dedicated "Code Analysis" section in our dashboard:

  • A list page (page.tsx) to view all analysis runs.
  • An "add repository" page (new/page.tsx) featuring a GitHub picker and manual entry options.
  • A detailed analysis page ([id]/page.tsx) with four tabs: Overview, Patterns, Docs, and a real-time Runs tab powered by our SSE stream, showing the log of the analysis in progress.

A new "Code Analysis" navigation entry with a Code2 icon was added to src/components/layout/sidebar.tsx, making the feature easily discoverable.

Phase 7: Testing – Ensuring Quality and Reliability

No feature is complete without thorough testing. We added 56 new unit tests across three files:

  • file-indexer.test.ts (26 tests)
  • pattern-detector.test.ts (17 tests)
  • doc-generator.test.ts (13 tests)

These tests, combined with our 15 pre-existing tests, bring our total to 71 passing tests, ensuring the reliability and correctness of our new AI-powered analysis engine.

Navigating the Treacherous Waters: Lessons Learned

Even with a clear plan, development always presents its unique set of challenges. These "pain points" often become our most valuable learning experiences.

Lesson 1: The Subtle Nuance of Set Iteration

The Challenge: While implementing deduplicatePatterns() in pattern-detector.ts, I instinctively used the spread syntax [...new Set([...arr1, ...arr2])] to merge and deduplicate arrays. The Hiccup: TypeScript threw TS2802: Type 'Set<string>' can only be iterated through when using the '--downlevelIteration' flag. Our tsconfig didn't have this flag enabled, and for good reason – it can increase bundle size and isn't always necessary for modern environments. The Workaround & Lesson: The fix was simple: Array.from(new Set([...arr1, ...arr2])). This avoids the direct spread iteration on Set and aligns with a known project convention documented in our CLAUDE.md. It was a good reminder that while modern JS features are great, understanding their compilation targets and project-specific configurations is crucial.

Lesson 2: The Perils of Relative Paths

The Challenge: When setting up the SSE route middleware, I initially imported it using ../../../../middleware, assuming it was deeper in the directory structure. The Hiccup: TS2307: Cannot find module – a classic "path not found" error. The Workaround & Lesson: A quick check revealed the code-analysis SSE route was at the same depth as our existing workflows SSE endpoint. The correct path was ../../../middleware. This highlighted the importance of consistent directory structures and double-checking relative paths, especially when copying patterns from existing features.

Lesson 3: Precision in Test Assertions

The Challenge: For our scoreDocQuality() function, I initially used toBeGreaterThan(0.7) in unit test assertions to validate quality scores. The Hiccup: Tests were failing unexpectedly. Upon investigation, scores were landing exactly on boundary values (e.g., 0.7). 0.7 is not greater than 0.7. The Workaround & Lesson: The solution was to change the assertion to toBeGreaterThanOrEqual(). This small change was a powerful reminder that precision matters in testing, especially when dealing with floating-point numbers or boundary conditions. Always consider edge cases!

What's Next? The Road Ahead

While the core feature is complete and pushed to main (commit 3aad552), the journey of continuous improvement never truly ends.

  1. RLS Policy Application: A critical manual step is to apply the RLS policies: psql $DATABASE_URL < prisma/rls.sql. This ensures our data security measures are active.
  2. Smoke and End-to-End Testing: We'll be doing a thorough smoke test (npm run dev, navigate to /dashboard/code-analysis) to verify the UI and empty state, followed by full end-to-end testing: adding a GitHub repo, triggering an analysis, and verifying SSE streaming, patterns, and documentation appear correctly.
  3. Scanner Flexibility: Currently, the scanner's fetchContent option is hardcoded. We'll consider exposing this as a configurable option when triggered from the analysis runner.
  4. Pattern Rules UI: The Extension Builder workflow prompts referenced a custom pattern rules UI. While the backend PatternRule interface exists, a user-facing editor was not implemented in this phase. This is a prime candidate for a future enhancement, allowing users to define their own custom code patterns directly within the detail page.

Conclusion

Building the AI-powered Code Analysis extension has been an incredibly rewarding experience, pushing the boundaries of what's possible with modern web technologies and artificial intelligence. From designing a robust database schema with Prisma and RLS, to building sophisticated AI services with LLMs, orchestrating complex pipelines with tRPC and SSE, and crafting an intuitive Next.js dashboard, every phase brought its own set of challenges and triumphs.

This feature represents a significant leap forward in helping developers understand, maintain, and improve their codebases. We're excited to see the insights it uncovers and the value it brings to our users.