ArchScan - Findings & quality report
ArchScan scans your codebase and produces a full-parity report with a single, category-weighted quality score.
Report sections
Section titled “Report sections”- Summary score - a single category-weighted score + status, comparable across versions.
- Findings - with code snippet and exact file location.
- SOLID breakdown - violations are broken down by the five principles.
- Packages - dependencies and versions.
- Projects - per-project metrics.
- Dependency map - an interactive graph view.
- Export - shareable HTML/PDF; you choose the report language per workspace.
How is the score calculated?
Section titled “How is the score calculated?”The architecture, SOLID, dependency and metric categories are reduced to a single score according to their weights. The scan runs in your own CI.
Analytics dashboard
Section titled “Analytics dashboard”The ArchScan landing page is a workspace-wide dashboard, loaded asynchronously so the page opens instantly:
- Projects audited, average score, open issues, audits this month - headline tiles.
- Scanning trend - runs and average score over the last 12 weeks.
- Where the estate stands - a grade ring (A-E), coverage and outcome bars, and a working-pace gauge that says how actively the workspace scans.
- Coverage breakdown - per language: projects, scanned share, average score, failures.
- What moved - the projects whose score improved or dropped most recently.
Code Health
Section titled “Code Health”ArchScan → Code Health answers a different question from the score: where the debt sits. It reads each project’s latest run - or, with All projects selected, every scanned project’s latest run merged:
- File risk map - a treemap where a tile’s size is concentrated risk (findings weighted by the file’s worst method) and its colour is the worst severity inside. Small files fold into an “others” tile so nothing is silently dropped.
- Complexity distribution - methods banded by cyclomatic complexity (1-5 / 6-10 / 11-20 / 21+), plus the single worst method by name.
- Design debt by principle - findings grouped by the principle their rule upholds (SOLID, DRY, KISS, YAGNI).
- Components on the main sequence - each module plotted by abstractness vs. instability; the zone-of-pain and zone-of-uselessness corners are labelled.
- Hotspots - files ranked by findings joined with complexity.
A project whose runner sends no metrics is reported as not measured - never as zero.
Dependency Health
Section titled “Dependency Health”ArchScan → Dependencies inverts the per-project package lists into estate-wide answers:
- The package table - keyed by package, not by project: which packages hold the most projects back, how far behind the worst pin is, and which projects use which version. A package pinned at several versions is flagged as split.
- Platform versions - the SDK/runtime each repository declares (from
csproj/global.json,pyproject.toml,go.mod,pom.xml,Gemfile,composer.json,package.json), so “who is still on .NET 6” is one glance. All seven collector languages report it. - Per-project rows - totals, currency mix, and unresolved lookups (an offline runner cannot ask the registry - that is reported as unknown, not as current).
SmartScan
Section titled “SmartScan”ArchScan → SmartScan follows every web entry point, Quartz job, hosted background service and MassTransit consumer of a .NET project down through services and handlers to the ORM (Entity Framework, Dapper), and reports the risks it sees on that chain. It is a warning module: it does not lower the score and does not fail the pipeline. A chain with no risk sends nothing.
- Record not found - a lookup (
FirstOrDefault,Find, …) can return nothing and the result is used without a check. The impact says whether the request ends in a 500 or a catch-all upstream turns it into a wrong message. - Half-written data - one request commits more than once with no transaction around the commits.
- Length beyond the column, missing required value, duplicate value - a request field reaches a limited,
NOT NULLor unique column with no matching rule on the way. - Request value the code cannot read (.NET, Java, Kotlin, Python, Go, Ruby, PHP) - a value the request sends is read with
int.Parse,Guid.Parse,Convert.ToInt32and the like with no check first, so a malformed value ends in a 500 instead of a 400. A row the request names read withFirst/Singleis reported under Record not found the same way: when it is not there, the request ends in a 500 instead of “not found”. - Delete blocked by related rows (.NET, Java, Kotlin, Python, Go, Ruby, PHP) - a row is really deleted (not a soft delete) while other rows may still point at it with
Restrict,NoActionorClientSetNull; the database refuses the delete with a foreign key error. Flows that check, delete or re-point those rows first are not reported. - Value outside what the column holds (.NET; the decimal case also Java, Kotlin, Python, Go, Ruby, PHP; the enum case also Ruby, PHP) - a number larger than its
decimal(p, s)column holds, a date the request may leave out landing in a SQL Serverdatetimecolumn as 0001-01-01, or an undefined value reaching a PostgreSQL enum column.
Pick a project, open a category, and risks are grouped by layer and service (or by request type). An opened risk shows the chain step by step with file and line, the code, a fix, and a prompt you can copy into an AI assistant. A risk can be closed (false positive, accepted risk, won’t fix) and stays closed while the same risk is found.
For a consumer the impact is a retried (or, behind a catch-all, swallowed) message; for a job, a failed run. A job has no request, so it is checked for half-written data only, and a commit per pass of a loop is a job’s design, not a risk.
In a TypeScript frontend (React, Next.js) the chain starts where the user acts or the framework renders - a JSX event handler, a useEffect, a server render (getServerSideProps, an async page), a Next.js route handler - and runs through hooks, Redux thunks and services to the request (fetch, axios), including the hop from a client request to the project’s own API route. The categories there: Unhandled request (a request failure nothing catches: the user is told nothing, or a server render answers with a 500), Failure passed on as success (a failure caught and returned as an ordinary value, a fetch error answer read as data, a thunk rejection nobody looks at), Record not found (that empty value then dereferenced, so the screen throws) and Half-written data (two writing requests to the own backend in a row). An HTTP client that shows every failure to the user (a toast in an interceptor) counts as handling.
Ruby on Rails and Kotlin Spring backends are walked like .NET: controller actions (and Grape endpoints), jobs and consumers down to ActiveRecord or Spring Data / JPA, with the same categories; column limits come from db/schema.rb or the @Column annotations. Android (Kotlin) and iOS (Swift) apps are walked like a frontend: from a tap, a screen’s lifecycle or a view model down to the request (Retrofit, OkHttp, Ktor; URLSession, Alamofire, Moya). There an unhandled request failure that escapes a coroutine, or a try!, crashes the app; a failure turned into nil or a default value is reported as passed on as success.
Java (Spring Data, JPA, JdbcTemplate, MyBatis), Python (Django and DRF, Flask, FastAPI, Celery; Django ORM, SQLAlchemy), Go (net/http, gin, echo, chi, fiber, gRPC; database/sql, sqlx, GORM, pgx) and PHP (Laravel, Symfony; Eloquent, Doctrine) backends are walked the same way, with the same categories. In Go a missing row is an error, not a null: a not-found error that is ignored, so that an empty row is used as if it had been found and the request answers success, is shown with the silent impact. Go also reports a write whose error is dropped (a bare db.Create(...), _ =, or an error that is only logged) while the request still answers success: the client is told the data was saved when it may not have been.
SmartScan is switched on per project, off by default: Project → Settings → Scanning → Run SmartScan. It then runs on that project’s deep scans from the next pipeline run, with nothing to change in the pipeline. It needs the SmartScan module, which an administrator can switch off under Management → Settings → Modules.