Metadata-Version: 2.4
Name: nspect
Version: 0.3.0
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Rust
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Utilities
License-File: LICENSE
Summary: Analyze the structure of C# projects and solutions: dependency graphs, package references, version conflicts, and structural metrics
Keywords: csharp,dotnet,static-analysis,dependency-graph,cli
Author-email: Ville Vainio <vivainio@gmail.com>
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/vivainio/nspect
Project-URL: Issues, https://github.com/vivainio/nspect/issues
Project-URL: Repository, https://github.com/vivainio/nspect

# nspect

A Rust CLI that analyzes the **structure** of C# projects and solutions: dependency graphs, package references, version conflicts, structural metrics (LOC / members / cyclomatic complexity), and a best-effort cross-check between declared packages and what the source actually imports.

Not a compiler, not Roslyn. Fast, read-only, works from the filesystem — no NuGet restore, no MSBuild evaluation.

## Install

### From a release binary

Linux / macOS:

```bash
curl -fsSL https://github.com/vivainio/nspect/releases/latest/download/nspect-linux-x86_64-musl -o ~/.local/bin/nspect && chmod +x ~/.local/bin/nspect
```

macOS (Apple Silicon):

```bash
curl -fsSL https://github.com/vivainio/nspect/releases/latest/download/nspect-macos-aarch64 -o ~/.local/bin/nspect && chmod +x ~/.local/bin/nspect
```

Windows (PowerShell):

```powershell
iwr https://github.com/vivainio/nspect/releases/latest/download/nspect-windows-x86_64.exe -OutFile ~/.local/bin/nspect.exe
```

### Via pip

```bash
pip install nspect
```

### From source

```bash
git clone https://github.com/vivainio/nspect
cd nspect
cargo install --path .
```

## Commands

### `nspect scan <path>`

List every project reachable from `<path>` (a repo root, a `.sln`, or a `.csproj`) with its SDK style, target framework(s), and package/project refs.

```
$ nspect scan ./my-repo
Found 12 project(s)

┌──────────────┬─────┬────────────────────────┬──────┬──────────┐
│ Project      │ SDK │ TargetFramework(s)     │ Pkgs │ ProjRefs │
╞══════════════╪═════╪════════════════════════╪══════╪══════════╡
│ Web.Api      │ sdk │ net8.0                 │ 14   │ 3        │
│ Domain       │ sdk │ net8.0, netstandard2.0 │ 2    │ 0        │
...
```

Add `--format json` for machine output.

### `nspect graph <path>`

Emit a project-to-project dependency graph as DOT, Mermaid, JSON, or a text summary.

```bash
nspect graph ./my-repo --format dot      | dot -Tsvg > graph.svg
nspect graph ./my-repo --format mermaid  > graph.mmd
nspect graph ./my-repo --format text
```

Package nodes are **off by default** — on large monoliths they drown out the project structure. Add `--packages` to include them.

### `nspect atlas <path>`

Emit a structural snapshot of the repo as YAML (default) or JSON: areas, projects with fan-in/fan-out/layer, and internal vs. external references.

```bash
nspect atlas ./my-repo                       # YAML to stdout
nspect atlas ./my-repo --format json         # JSON
nspect atlas ./my-repo --check               # embed findings (see below) under `findings:`
nspect atlas ./my-repo --output-dir ./out    # writes multiple artifacts (see below)
```

With `--output-dir`, the tree-sitter source scan runs and these artifacts are written side by side:

| File | Contents |
|---|---|
| `atlas.yaml` | Project graph (areas, fan-in/fan-out, layers, refs). Each project also gains a `weight:` block with aggregate `types`, `loc`, `members`, `complexity`. |
| `classes.yaml` | Declared types per project, grouped by namespace and bucketed by kind (`class`, `interface`, `struct`, `record`, `record_struct`, `enum`, `delegate`). Nested types keep a dotted local path (e.g. `Outer.Inner`). |
| `metrics.yaml` | Same shape as `classes.yaml` but values are `{loc, members, complexity, methods}` per type, plus a per-project `totals:` block. |
| `checks.yaml` | Only written with `--check`. The `findings` list (see below) as a standalone artifact. |
| `tips.yaml` | Soft architectural suggestions (e.g. `merge_candidates`). Non-authoritative — meant for human review, not CI gates. |

Without `--output-dir`, only the atlas itself is emitted (to stdout) and no source scan is performed — unless `--check` forces it to run the package-ref heuristics.

#### `--check` findings

When `--check` is on, `atlas.yaml` gains a `findings:` array (and with `--output-dir`, a sibling `checks.yaml`). CI gates can grep for error-severity kinds.

| Finding | Severity | What it means |
|---|---|---|
| `cycle` | error | A project-to-project reference cycle. |
| `version_conflict` | error | Same package declared with different versions across projects. |
| `unresolved_project_ref` | warning | A `<ProjectReference>` that doesn't resolve on disk. |
| `unused_package_ref` | warning | A `<PackageReference>` whose namespaces never appear in any `using` of the project. Skips test runners, analyzers, and runtime shims by default. |
| `undeclared_usage` | warning | A `using X.Y.Z;` that doesn't match any declared package or project ref. Advisory only — noisy on legacy codebases that rely on transitive DLL discovery. |
| `orphan_project` | info | A project with no incoming or outgoing project refs. |

### `nspect metrics <path>`

Fast text summary of structural metrics — runs the tree-sitter pass, prints a per-project table plus the top methods by cyclomatic complexity. Scope is whatever csprojs live under `<path>`, so you can point it at a single `.csproj`, a subdirectory, or the whole repo:

```
$ nspect metrics tests/fixtures/sourcescan --top 5
project        types      loc  members  complexity
--------------------------------------------------
App                1        9        1           0
--------------------------------------------------
TOTAL              1        9        1           0

top 1 methods by complexity:
           0      6  App.Program.Main
```

When the scope contains more than one project, each method in the top-N section is prefixed with `project::` to disambiguate.

Flags:

- `--top <N>` — how many top methods to list (default 20, `0` disables the section)
- `--project <name>` — restrict the methods section to a single project (exact, suffix, or substring match)

Metric definitions:

- **loc** — source lines spanned by the type / method declaration.
- **members** — direct methods, properties, fields, ctors, events, indexers. Nested types are *not* counted as members.
- **complexity** — McCabe-ish branch count: `if`, `while`, `for`, `foreach`, `do`, `case`, `catch`, ternary `?:`, `when` clauses. Logical `&&` / `||` are currently **not** counted. Branches inside nested types count toward the enclosing type.

### `nspect ts-dump <file.cs>`

Debug aid. Shows the extracted `using`s, top-level named children of the parse tree with line ranges, and (with `--sexp`) the full tree-sitter S-expression annotated with leaf source text:

```
(class_declaration
  (modifier "public")
  name:
  (identifier "Greeter")
  body:
  (declaration_list
    (method_declaration ...)))
```

Useful for writing new heuristics against the CST.

### `nspect focus <path> <project>`

Visualize the dependency neighborhood of a single project: `--up <N>` hops of reverse refs (projects that depend on it) and `--down <N>` hops of forward refs (projects it depends on), each defaulting to `1`. `<project>` matches by exact name, suffix, or unique substring. `--format` accepts `dot` / `mermaid` / `json` / `text` (default).

```bash
nspect focus ./my-repo Domain --up 2 --down 1 --format text
```

### `nspect init [path]`

Bootstraps `nspect` in a repo: creates `.nspect/gen/`, adds `/.nspect/gen/` and `/.nspect/cache/` to `.gitignore` (the derived artifacts — `spec/` is hand-authored and meant to be committed), seeds `.nspect/spec/areas.yaml` (and a rules stub), and populates `gen/` with a full atlas (`--check --references`, source scan included). Run this once so `nspect lookup` has something to read. `[path]` defaults to the current directory.

```bash
nspect init ./my-repo
```

### `nspect lookup <names...>`

Reports everything the `.nspect/gen/` artifacts know about a type: declaring project, namespace, metrics (loc/members/complexity), and cross-project callers. Reads `atlas.yaml` / `classes.yaml` / `metrics.yaml` / `references.yaml` from `--atlas-dir`, or walks up from the current directory looking for `.nspect/gen` (as produced by `nspect init`) if omitted.

```bash
nspect lookup Customer OrderService
nspect lookup --file Customer.cs
```

Names may be simple (`Customer`) or fully-qualified (`Acme.Domain.Customer`); multiple names combine freely with `--file` (repeatable, suffix match on the source path). `--no-sig` skips the tree-sitter re-parse that resolves method signatures (useful if the source tree has drifted); `--min` trims output to method names and line ranges only.

### `nspect install-skills`

Installs the bundled Claude Code skill (`SKILL.md`) so agents know when and how to use `nspect lookup`. Installs to `~/.claude/skills/nspect/` by default, or `.claude/skills/nspect/` in the current repo with `--project`.

```bash
nspect install-skills --project
```

### `nspect check-bindings <path>`

Checks `app.config` / `web.config` / `*.exe.config` `<bindingRedirect>` entries for inverted redirects, cross-file inconsistencies, and in-file duplicates. Same checks as `atlas --check`, but standalone and much faster since it skips the project graph findings, package heuristics, and source scan.

```bash
nspect check-bindings ./my-repo --format yaml
```

Add `--dlls <bin-dir>` to also scan a built output directory (recursively) and cross-reference each assembly's `AssemblyRef` table against the redirects found — this shows which real on-disk reference actually needed each redirect. Opt-in, since unlike the rest of the command it requires binaries to have already been built. `--format` accepts `text` (default) / `json` / `yaml`; `--compact` emits single-line JSON.

### `nspect dlls <path>`

Parses `.dll`/`.exe` build output and dumps each assembly's identity and `AssemblyRef` dependencies by reading ECMA-335 metadata directly — no `dotnet`/CLR/Mono involved. `<path>` is a single binary or a directory to scan recursively (typically a `bin/` folder).

```bash
nspect dlls ./my-repo/Web.Api/bin/Debug/net48 --format yaml
```

`--format` accepts `yaml` (default) / `json` / `text`; `--compact` emits single-line JSON. Public key tokens are stripped by default (`--pkt` to keep them) since nearly every framework reference carries the same handful of well-known Microsoft tokens.

## What it handles

- **SDK-style csproj** — `<PackageReference>`, `<ProjectReference>`, `TargetFramework(s)`, `AssemblyName`
- **Legacy csproj** — `<Reference>` assembly refs are counted as namespace providers
- **`.sln` files** — project list (the format is not XML; parsed directly)
- **Central Package Management** — walks up for `Directory.Packages.props` and resolves version-less `PackageReference` entries
- **Multi-targeting** — captures `TargetFrameworks="net8.0;netstandard2.0"` as a list
- **Malformed csprojs** — skipped with a warning instead of aborting the scan

## What it doesn't handle

- **MSBuild property evaluation.** `$(Foo)` references are recorded as-is; nothing is expanded. Attempting to evaluate MSBuild correctly is a rabbit hole.
- **`Directory.Build.props/targets`.** Presence is not currently merged into project metadata. Flagged for a future milestone.
- **Transitive DLL discovery via HintPath.** Legacy .NET Framework monoliths rely on `packages/*/lib/*.dll` being found through a chain of HintPaths. The `undeclared_usage` finding does not trace these, which is why it's noisy on legacy codebases.
- **Type resolution.** The source scan is textual. `using Foo.Bar;` produces the string `"Foo.Bar"`; whether that's a namespace or a static type is not determined.
- **NuGet restore.** `nspect` analyzes what's *declared*, not what would *resolve*.

## Performance

On a ~790-csproj monolith:

| Command | Time |
|---|---|
| `nspect scan` (parse all csprojs + CPM) | ~0.3 s |
| `nspect graph` | ~0.3 s |
| `nspect atlas` | ~0.3 s |
| `nspect atlas --check` (tree-sitter across ~all .cs files) | ~28 s |
| `nspect atlas --output-dir ...` (full source scan + per-type metrics) | ~28 s |

## License

MIT

