The Question I Kept Answering By Gut Feel
For more than a decade, I have opened codebases and been asked some version of the same question.
Is this architecture healthy?
Should we refactor now or later?
Is this technical debt or is this fine?
Every time, I answered from experience.
I looked at the folder structure.
I looked at how components were organized.
I looked for circular dependencies I half-remembered fixing somewhere else.
I looked for the one file everything seemed to import.
Then I gave an opinion.
The opinion was usually right. But it was never something I could hand to someone else. It lived in my head, not in a report. Two engineers looking at the same codebase could walk away with two different answers, and neither of us could point to a number and say "here is why."
Linters solve a different problem. ESLint tells you about code style. SonarQube tells you about code quality and duplication. None of them answer the actual question a tech lead asks in a planning meeting.
Is this architecture healthy, and what should we fix first?
That gap is what became Arcovia.
The Trigger: OpenAI Build Week
I did not sit down one weekend and decide to build an architecture scoring engine from a blank page.
I built the first version of Arcovia during OpenAI Build Week, a hackathon with a hard deadline and no room for a slow start.
A hackathon forces a specific kind of clarity. You cannot explore five ideas. You pick one, and you pick it fast, because the clock does not wait for you to feel ready.
I had a shortlist of ideas that week. Most of them were the usual hackathon fare: another AI wrapper, another chat interface bolted onto an existing workflow.
I picked the one that had been bothering me professionally for years instead.
Not "add AI to X."
Turn architectural judgment into something measurable.
That constraint, ship something real in days, not months, is the reason Arcovia exists in the form it does today. It is also the reason I made one decision early that shaped everything downstream: the scoring itself would not use AI at all.
What Arcovia Actually Does
Arcovia is a CLI that analyzes a React, Next.js, or Vite project and produces an architecture report.
npx arcovia analyze .
Run against a real project, it builds a dependency graph, evaluates a set of architectural rules against that graph, and generates two artifacts:
report.html: an interactive, offline architecture reportanalysis.json: a versioned, machine-readable analysis artifact
The HTML report shows an overall architecture score and letter grade, a visible breakdown of how that score was calculated, a filterable list of findings with locations and recommendations, a dependency graph with your code separated from external packages, category-level health, and a roadmap of what to fix first.
None of it requires an account, a server, or a network connection. Nothing leaves your machine.
That last point is not a minor detail. It is the core positioning decision behind the whole project.
Deterministic by design. Every score, finding, and recommendation is generated through static analysis, not AI, so results stay consistent, reproducible, and explainable.
Ask Arcovia to analyze the same project twice, unchanged, and you get the same score twice. Ask two different tools that wrap an LLM around your codebase, and you might get two different opinions phrased with equal confidence. For something a team will use to prioritize real refactoring work, I wanted the former. Explainability matters more than eloquence when the output decides what a team spends its next sprint on.
$ npx arcovia analyze . Analyzing project...✔ Parsed 142 modules✔ Built dependency graph✔ Evaluated 38 architecture rules Architecture Score 82.4 / 100 Grade B+ Category HealthArchitecture 88% Imports 91%Components 79% Complexity 74%Hooks 85% Performance 80%Context 95% Routes 90% 3 critical findings 12 warnings 27 quick wins Report written to .arcovia-report/report.htmlDesigning the Scoring Model
The easy part of Arcovia was detecting problems. Circular dependencies, unused exports, and oversized components: static analysis has been finding these for years.
The hard part was turning a pile of findings into a single number that a tech lead could trust.
My first instinct, like most people's, was to count findings and subtract. More findings, lower score. That approach breaks almost immediately in a real codebase. A project with three hundred nearly identical "avoid inline object literals" warnings would score worse than a project with five unresolved circular dependencies threading through its core modules, even though the second project is the one actually in trouble.
Counting treats every finding as equally important. Real architecture debt is not distributed like that.
So the score is built in layers instead of one flat subtraction:
100
− category deductions
− maintenance-burden adjustment
− critical-risk adjustment
= final architecture score
Category deductions are weighted, not equal. Architecture-level findings (circular dependencies, god modules, structural boundary violations) carry 30% of the weight. Import hygiene and component design each carry 15%. Complexity, hooks, and performance each carry 10%. Context usage and routing carry 5% each. A codebase can have messy imports and still be considered fundamentally healthy. A codebase with a tangled dependency graph at its core cannot, and the weighting reflects that.
Repeated findings use diminishing penalties. The first instance of a pattern costs more than the fiftieth. This exists specifically to stop the failure mode I described above: a large codebase with one systemic style issue repeated three hundred times should not out-score a smaller codebase with a handful of genuinely dangerous structural problems.
Maintenance burden is tracked as a separate, capped adjustment layered on top of category health. This solves the opposite failure mode: a project with a wall of minor warnings and informational debt should not look flawless just because none of it is individually critical, but it also should not be scored as if the codebase were unsalvageable.
Critical risk gets its own bounded adjustment: up to four verified critical findings, weighted 10, 8, 6, and 4 points, capped at 28 total. Critical issues matter, but even a codebase with many critical findings should not mathematically bottom out at zero. A score needs somewhere left to go, or it stops being useful as a signal of progress.
The result maps to letter grades, from A+ at 97 and above down to F below 40, familiar enough that a score means something the moment you see it, without reading documentation first.
Designing this took longer than writing the static analysis engine that feeds it. A scoring model is itself an architecture decision. Get the weights wrong and the tool either cries wolf on cosmetic issues or stays silent while real debt accumulates. I rewrote the formula more times than I rewrote the AST traversal code.

The Dependency Graph Problem
Underneath the scoring model sits a dependency graph, and getting that graph right across real-world React and Next.js codebases turned out to be its own project.
Next.js Kept Looking Like Dead Code
The first real wall I hit was false positives from Next.js's own conventions.
A naive "unused export" check flags any exported symbol that nothing else in the codebase imports. That works fine for a typical utility module. It falls apart the moment Next.js enters the picture, because the framework itself is the consumer of a huge number of exports that never show up as an explicit import anywhere in your code.
Route handlers export GET and POST functions that Next.js's router calls directly. Pages export generateMetadata, generateStaticParams, and revalidate that the framework reads by convention, not by import. To a generic static analyzer, every one of those looks like dead code sitting unused in the graph.
Flag those as unused exports and the tool loses trust in the first five minutes someone runs it against a real Next.js app. Nobody keeps using an architecture tool that tells them to delete their route handlers.
The fix was making the analyzer framework-aware rather than framework-agnostic. Arcovia recognizes Next.js's own export conventions (App Router route handler methods, metadata and static-params exports, revalidate) and excludes them from unused-export findings specifically. It sounds like a small carve-out. In practice, it was the difference between a tool that felt reliable and one that generated noise on the exact kind of project it was built to analyze.
Finding Real Signal at Scale
The second wall was scale. Circular dependencies, god modules, and high fan-in/fan-out modules are all straightforward to define on paper and much harder to surface usefully once a codebase has thousands of modules.
A large app can have dozens of technically-circular import chains that are harmless (two files in the same feature folder that reference each other's types, for instance), buried alongside two or three genuinely dangerous cycles that touch core business logic and make the codebase brittle to change. Report all of them with equal weight and the real problems disappear into the noise of the harmless ones.
Fan-in and fan-out needed the same discipline. A shared UI primitive is supposed to have high fan-in: that's what makes it a good abstraction, not a red flag. A feature-level module with the same fan-in usually means something else has gone wrong: boundaries that should exist don't, and half the codebase now depends directly on internals it shouldn't know about.
The dependency graph had to carry enough context (module role, depth, position relative to app entry points) for the rules layered on top of it to tell those cases apart. That context is also what makes the category weighting in the scoring model possible in the first place. Get the graph wrong and every layer built on top of it inherits the mistake.

Building It With GPT Codex
The engine, the scoring model, and the framework-aware rules were mine to design. The implementation moved as fast as it did because of GPT Codex, running the 5.6 model, which I used for the bulk of the coding work during and after Build Week. ChatGPT generated the visual assets: the banner, the logo, and the supporting graphics you see across the repo and docs site.
This was a different workflow than how I've built other projects. I own the architecture decisions and the scoring design; Codex handled implementation at a pace that made a hackathon deadline realistic in the first place. The judgment calls (how much a category should weigh, whether a pattern deserved a diminishing penalty, whether a Next.js export was a framework convention or genuine dead code) stayed with me. Those are product decisions disguised as engineering ones, and no amount of implementation speed replaces having to make them.
What AI-assisted coding bought me here wasn't the idea. It was enough velocity to go from "a scoring model sketched during a hackathon" to "a published, working CLI" without months in between.
Shipping It
Arcovia is published on npm and installable today.
npx arcovia analyze .
# or install globally
npm install -g arcovia
arcovia analyze .
By default it writes to .arcovia-report/ in the analyzed project: analysis.json and report.html, plus an archived history directory so repeated runs build a score timeline over time. Run it weekly or in CI and the HTML report starts showing whether your architecture is actually trending better or worse, not just what it looks like today.
The docs live at arcovia.ghazikhan.in, covering the full rule set, the scoring model in more depth than I've gone into here, and the custom policy rules system for enforcing project-specific module boundaries.
The code is open source under MIT at github.com/gkhan205/arcovia.
The Honest Distribution Lesson
I want to be straight about where Arcovia actually stands, because most tool launch stories skip this part.
I posted about Arcovia and it did reach: 187.7K impressions on one post, 105 upvotes, 11 comments. On paper that looks like a good day. Look closer and the engagement rate is roughly 0.06%, and over 90% of that traffic came from a single referring source. Organic Google search sent about nine visits. A ProductHunt launch added 20 upvotes on top.
The package itself sits at v0.2.0, around 667 downloads a month, 51 GitHub stars, 5 forks. Reasonable for an early-stage tool. But converting 187.7K impressions into 51 stars is a conversion rate that tells its own story: reach on a feed is not the same thing as someone deciding to trust your tool.
A feed post gets scrolled past in a second. It cannot explain why a scoring formula subtracts a maintenance-burden adjustment separately from category deductions, or why a Next.js route handler shouldn't be flagged as dead code. It cannot build the kind of understanding that makes someone actually run npx arcovia analyze . against their own production codebase.
That is a large part of why this post exists. Impressions fade from a feed within a day. A detailed, honest writeup of how something was actually built tends to keep showing up in search results long after the original post has scrolled off everyone's timeline. If Arcovia is going to find the people who'd actually use it, I'd rather it happen through content like this than another burst of low-conversion reach.
What's Next for Arcovia
The core of Arcovia (the graph, the rule set, the scoring model) is stable enough to trust today. The directions I'm exploring next:
Broader framework support. Remix support is currently experimental. Extending the same framework-aware treatment Next.js got to more of the React ecosystem is the natural next step.
Deeper CI integration. The report history and timeline already exist. Turning that into something that can gate a pull request on architecture regressions, not just report on them after the fact, is the logical extension.
More policy presets. Custom policy rules via .arcovia.json already let teams enforce their own module boundaries. A library of common presets (layered architecture, feature-sliced design, hexagonal boundaries) would lower the barrier to adopting them.
Peer benchmarking with real data. The benchmark comparison already exists as a feature; what it needs now is a broader, versioned sample of real projects behind it instead of a single baseline profile.
None of this is a promised roadmap. It's the direction the tool is pulling toward as more real codebases run through it.
Final Thoughts
Arcovia started as a hackathon idea built to fit inside a deadline. What made it worth continuing afterward wasn't the deadline. It was that the underlying question, "is this architecture healthy, and what should we fix first," is one I've been asked in some form throughout my entire career, and I finally had a tool that could answer it the same way twice.
The scoring model is the part I'm proudest of, and also the part that took the most iteration. Detecting problems in a codebase is table stakes for a static analysis tool. Turning those problems into one fair, explainable number that a team can act on is a genuinely different problem, and it's the one that actually matters if the goal is changing what a team decides to work on next.
If you maintain a React, Next.js, or Vite codebase, the fastest way to see where this leads is to run it against your own project:
npx arcovia analyze .
You can find the source at github.com/gkhan205/arcovia and the full documentation at arcovia.ghazikhan.in.
If you try it and have thoughts, on the scoring model, a false positive I've missed, or a rule you think is missing, open an issue on the repo. It's still early, and the feedback that shapes what gets built next is exactly the kind that doesn't show up in an impressions count.
Building frontend systems at scale for the past decade. Staff Engineer, occasional writer, and someone who still finds CSS genuinely interesting. Currently helping companies move fast without breaking their architecture.



