# Typescript Audit

> Audit a TypeScript project's type discipline against an opinionated baseline spanning compiler configuration, type quality in source, type system usage, and type safety at IO boundaries. Static-first with optional --with-run enrichment from tsc --noEmit. Optionally generates an implementation plan for the gaps.

- **Type:** Skill
- **Install:** `agentstack add skill-bensheridanedwards-architectplaybook-typescript-audit`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [BenSheridanEdwards](https://agentstack.voostack.com/s/bensheridanedwards)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [BenSheridanEdwards](https://github.com/BenSheridanEdwards)
- **Source:** https://github.com/BenSheridanEdwards/ArchitectPlaybook/tree/main/typescript-audit

## Install

```sh
agentstack add skill-bensheridanedwards-architectplaybook-typescript-audit
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# /typescript-audit

Audit a TypeScript project's type discipline against an opinionated baseline organised in four layers — **compiler configuration**, **type quality in source**, **type system usage**, **type safety at boundaries** — preceded by a diagnostic snapshot. Then offer to generate an implementation plan for the gaps.

The default mental model is TypeScript and React. Layers 1, 2, 3 apply to any TypeScript codebase; layer 4's IO-boundary checks lean toward frontends and full-stack applications, but most checks apply equally to backend TypeScript.

## How this differs from neighbouring audits

| Concern | Owner |
| --- | --- |
| Whether `tsc --noEmit` *runs* at every lifecycle stage | `/quality-gates-audit` |
| `@typescript-eslint` plugin coverage and rule selection | `/linting-audit` |
| `incremental` and project references for *build performance* | `/bundle-build-audit` |
| `useUnknownInCatchVariables` *as it affects catch typing* | `/error-handling-audit` |
| **Whether the compiler is configured strictly** | `/typescript-audit` |
| **Quality of types written in source** (`any`, assertions, `@ts-ignore`) | `/typescript-audit` |
| **Type system usage** (discriminated unions, branded types, utility types) | `/typescript-audit` |
| **Type safety at IO boundaries** (runtime validation of network/form/storage data) | `/typescript-audit` |

When a single fix passes multiple audits — for example, enabling `useUnknownInCatchVariables` satisfies both `/error-handling-audit` (which checks the resulting catch typing) and `/typescript-audit` (which checks the compiler flag) — every relevant audit surfaces the same gap so the user sees it once and resolves it once.

## Static-first design with optional run enrichment

This skill is read-only and never modifies anything. Two modes:

- **Static (default).** Read `tsconfig.json` (and any `extends`-chained configs), `package.json`, and source files. Pattern-detect type-quality signals across `.ts` and `.tsx` files.
- **Static plus opt-in `--with-run`.** Invoke `npx tsc --noEmit` (no emit, no side effects) and parse the diagnostics. Provides a real error count and the actual locations of every type error, which sharpens layer 2 substantially.

`tsc --noEmit` is genuinely read-only — it does not write to `dist/`, `.next/`, or anywhere else. The skill never invokes the compiler with any other flag, never installs anything, and never edits configuration.

## Usage

```
/typescript-audit                                 # default: concise Top 5 + full report saved + ask about plan
/typescript-audit --worktree                          # create an isolated Git worktree, then run the audit there
/typescript-audit --learn                         # mid-level engineer teaching mode (detailed explanations + file/line examples)
/typescript-audit --teach                         # alias for --learn
/typescript-audit --with-run                      # static plus enrichment from tsc --noEmit
/typescript-audit --threshold-any-per-file=5      # override default 3
/typescript-audit --threshold-as-per-file=10      # override default 5 (excluding `as const`)
/typescript-audit --threshold-non-null-assertion-per-file=5  # override default 3
/typescript-audit --threshold-conditional-type-depth=4       # override default 3
```

**💡 Pro tip**: Add `--worktree` to run this audit in an isolated Git worktree.

The skill never accepts `--apply`. The implementation plan is descriptive Markdown.

**💡 Pro tip**: Run `/preflight --audit=typescript` first to detect — and optionally install — the development dependency that makes `--with-run` useful (`typescript`, which provides `tsc --noEmit`). Skip if you already know the tooling is wired up.

## The opinionated baseline

A check resolves to one of four statuses:

- **present** — the invariant holds.
- **partial** — most signals resolve, with a small number of exceptions, or the codebase shows mixed adherence to a soft check.
- **missing** — a structural prerequisite is absent (no `tsconfig.json`, for example — the skill stops earlier in that case, but the status is reserved).
- **violation** — the audit identified concrete configuration or source that breaks the invariant.

Layer 0 is informational only and has no status.

### Layer 0 — Diagnostic snapshot (always written, no pass/fail)

- TypeScript version (resolved from the lockfile).
- `tsconfig.json` path(s); when `extends` is used, the resolved final configuration is recorded.
- Detected runtime validation library: zod, valibot, arktype, io-ts, yup, joi, superstruct, runtypes — or none.
- Strict-flag adoption: which of `strict`, `noImplicitAny`, `strictNullChecks`, `strictFunctionTypes`, `strictBindCallApply`, `strictPropertyInitialization`, `noImplicitThis`, `alwaysStrict`, `useUnknownInCatchVariables`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noImplicitReturns`, `noFallthroughCasesInSwitch`, `noImplicitOverride` are enabled.
- Type-quality counts across source: `any` annotations, `unknown` annotations, `as` assertions (excluding `as const`), `as const` assertions, non-null assertions (`!`), `@ts-ignore`, `@ts-expect-error`, `@ts-nocheck`.
- Top 10 files by `any` count, top 10 by `as` count, top 10 by `!` count.
- Project references count (composite projects in monorepos).
- `tsc --noEmit` total error count when `--with-run`.

### Layer 1 — Compiler configuration

| Check | Expectation | Violation signal |
| --- | --- | --- |
| `strict: true` | The strict family is enabled wholesale via the `strict` flag. | `strict: false`, or `strict` absent and individual strict-family flags missing. |
| `noUncheckedIndexedAccess` enabled | Distinguishes `array[i]: T` from `array[i]: T \| undefined`, catching a major source of runtime errors that the compiler otherwise ignores. | Flag missing or `false`. |
| `exactOptionalPropertyTypes` enabled | Distinguishes `prop?: T` from `prop: T \| undefined`. Soft check — reported as `partial` because some codebases legitimately can't enable it without significant refactoring. | Flag missing or `false`. |
| `noImplicitReturns` enabled | Catches functions that fall off the end without returning when their type says they should. | Flag missing or `false`. |
| `noFallthroughCasesInSwitch` enabled | Catches missing `break` or `return` in switch cases. | Flag missing or `false`. |
| `useUnknownInCatchVariables` enabled | Catch variables default to `unknown`, not `any`. (Overlap with `/error-handling-audit`; both surface so a single fix passes both.) | Flag missing or `false`. |
| `noImplicitOverride` enabled | Class methods that override a parent must use the `override` keyword. Soft check — reported as `partial` for codebases with no classes. | Flag missing in a class-using codebase. |
| `isolatedModules` enabled | Required by every modern bundler (Vite, esbuild, swc, Bun) for safe per-file transpilation. | Flag missing or `false` in a project using one of those bundlers. |
| `skipLibCheck` is a deliberate choice | The flag is either explicitly `true` (with the awareness that third-party `.d.ts` files won't be type-checked) or explicitly `false`. The check verifies the choice was made; it doesn't pick a side. Soft check. | The flag is absent and the default behaviour applies (which differs across TypeScript versions). |
| `target` and `module` appropriate | `target` matches the runtime (`ES2022` or newer for modern Node and modern browsers). `module` is `ESNext`, `NodeNext`, or `Preserve` — not `CommonJS` for browser code. | `target: 'ES5'` or older in a project that ships modern environments only; or `module` mismatched against the resolution model. |
| `moduleResolution` set explicitly | `moduleResolution` is `Bundler` (Vite, esbuild) or `NodeNext` (Node), set explicitly rather than defaulting. | Flag absent. |
| Composite or project references for monorepos | Monorepos use TypeScript project references (composite projects) so type-checks scale across packages. Skipped silently outside monorepos. Soft check. | Multi-package project without project references. |

### Layer 2 — Type quality in source

| Check | Expectation | Violation signal |
| --- | --- | --- |
| `any` usage is rare | The total `any` annotation count per file is at or below the threshold (default 3; tunable via `--threshold-any-per-file`). The audit counts both scalar `any` and `any[]`. | Files exceeding the per-file threshold. |
| `unknown` preferred over `any` for opaque values | `unknown` appears in the codebase as the safe alternative to `any` when typing genuinely opaque shapes. Soft check — reported as `partial` when no `unknown` appears at all in a non-trivial codebase. | A codebase with substantial `any` usage and zero `unknown`. |
| `@ts-ignore` replaced by `@ts-expect-error` with description | No `@ts-ignore` directives remain; all suppressions are `@ts-expect-error` with a free-text justification on the same or adjacent line. | Any `@ts-ignore` in source. |
| No `@ts-nocheck` at file level | No file disables type checking entirely. | Any `@ts-nocheck` at the top of a source file. |
| Type assertions (`as`) are rare | The total `as` assertion count per file is at or below the threshold (default 5; tunable via `--threshold-as-per-file`). The audit recognises that `as const` is not a type assertion in the dangerous sense and excludes it from the count. | Files exceeding the per-file threshold. |
| Non-null assertions (`!`) are rare | The total non-null assertion count per file is at or below the threshold (default 3; tunable via `--threshold-non-null-assertion-per-file`). | Files exceeding the per-file threshold. |
| No `Function` type | Use specific function signatures, not the unsafe `Function` type (which permits any callable). | Any `Function` annotation in source. |
| No `Object` type | Use `Record`, a specific shape, or `object` (lowercase). | Any `Object` annotation in source. |
| No primitive wrapper types | Use `string`, `number`, `boolean` — not `String`, `Number`, `Boolean` (the wrapper-object types). | Any wrapper-type annotation in source. |
| No empty interfaces | Interfaces declare at least one member, or extend another type. An empty interface either does nothing or is a same-shape alias for what it extends. | `interface Foo {}` declarations. |

### Layer 3 — Type system usage

| Check | Expectation | Violation signal |
| --- | --- | --- |
| Discriminated unions for state machines | Multi-stage state is modelled as a discriminated union (`{ status: 'idle' } \| { status: 'loading' } \| { status: 'success'; data: T } \| { status: 'error'; error: E }`), not as multiple booleans or wide objects with optional fields. (Overlap with `/react-audit`'s "status enums over multiple booleans"; both surface.) Soft check — reported as `partial`. | Multi-state shapes hand-rolled with several optional fields and no discriminant. |
| Branded or nominal types for IDs | Identifier types (`UserId`, `OrderId`, `WorkspaceId`) are branded so the compiler distinguishes `UserId` from `OrderId` even though both carry `string` at runtime. Soft check — reported as `partial`. | Identifier types declared as bare `string` aliases. |
| `as const` for literal preservation | Configuration objects, route lists, and similar literal values use `as const` so the compiler infers the narrow literal type rather than widening to `string` or `number`. Soft check — reported as `partial`. | Patterns where `as const` would tighten inference and is missing. |
| Utility types preferred over hand-rolled | Use `Pick`, `Omit`, `Partial`, `Required`, `Readonly`, `Record`, `NonNullable`, `Awaited`, `ReturnType`, `Parameters` rather than hand-rolling equivalents. | Hand-rolled mapped types that duplicate a built-in utility. |
| Generic constraints used | Generic parameters that are operated on with `.length`, indexing, or property access have `extends` constraints, not bare `T`. | Bare `T` generics whose body operates on the parameter assuming a shape. |
| Conditional types used judiciously | Conditional types in source are bounded — no nested-deeper-than-threshold conditional chains in application code. The threshold is tunable via `--threshold-conditional-type-depth=N` (default 3). The audit isn't strict about this; library code can warrant complexity. Soft check — reported as `partial` when the depth threshold is exceeded. | Nested conditional types deeper than the threshold. |

### Layer 4 — Type safety at boundaries

| Check | Expectation | Violation signal |
| --- | --- | --- |
| Runtime validation library installed | One of zod, valibot, arktype, io-ts, yup, joi, superstruct, runtypes is in `dependencies`. Soft check — reported as `partial` when absent (a project may legitimately have no untyped IO boundaries beyond a fully-typed framework like tRPC end-to-end). | None present in a project that performs network requests, parses JSON, or reads form/URL/storage data. |
| Network response data validated | Calls to `fetch`, `axios`, framework data primitives have their JSON parsed through a validator (zod schema, etc.) before being typed. The audit recognises framework-level type-safe layers (tRPC, GraphQL Code Generator outputs) as equivalent. | Network responses cast directly with `as` or implicitly typed without runtime validation. |
| `JSON.parse` results validated | `JSON.parse(...)` outputs are validated, not typed by direct assertion. | `JSON.parse(...) as MyType` patterns. |
| Form data validated | Forms validate input with the form library's schema integration (react-hook-form + resolver, formik + Yup, equivalent) or a standalone validator. (Overlap with `/react-audit`'s form-library check; both surface.) | Form data passed straight to a typed handler with no validator. |
| URL parameters validated | Router-extracted params (`useSearchParams`, `useParams`, `router.query`) are validated before being typed as anything stricter than `string` or `string[]`. | URL parameters cast to specific types or assumed narrower than their actual runtime type. |
| `localStorage`/`sessionStorage` reads validated | Storage reads are parsed *and* validated before being typed. | `JSON.parse(localStorage.getItem('key')!)` patterns with no validator. |
| Environment variables validated | Environment-variable access (`process.env`, `import.meta.env`) goes through a validated configuration module — not direct reads scattered across the codebase. Soft check — reported as `partial`. | Direct `process.env.X` reads in feature code rather than a validated config module. |

## What this skill does

1. **Reads the knowledge graph when present.** Soft dependency: when `graphify-out/graph.json` exists, the audit cross-references type-quality offenders (high-`any`, high-`as`, high-`!` files) against graph centrality and prioritises god nodes in the implementation plan. The audit still runs in full when the graph is absent.
2. **Confirms a TypeScript project.** Detects `package.json`, `tsconfig.json`, and `typescript` in dependencies. If any are absent, the skill stops and tells the user it currently supports TypeScript projects only.
3. **Resolves the effective `tsconfig.json`** by following any `extends` chain. Records the final resolved configuration in metadata.
4. **Detects the runtime validation library** (zod, valibot, arktype, io-ts, yup, joi, superstruct, runtypes) for the diagnostic snapshot and for the layer 4 checks.
5. **When `--with-run` is set**, invokes `npx tsc --noEmit` and parses the diagnostics. If `tsc` fails to start (not installed in the project), records the failure, prints to the chat and prepends to `findings.md`: "`--with-run` was requested but `typescript` is not installed. Run `/preflight --audit=typescript --install` to install it, then re-run this audit. The static analysis has been completed; only the run-dependent enrichment degraded to `partial`." Records `recoveryHint: "/preflight --audit=typescript --install"` on each run-dependent check that degraded in `findings.json`. Continues w

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [BenSheridanEdwards](https://github.com/BenSheridanEdwards)
- **Source:** [BenSheridanEdwards/ArchitectPlaybook](https://github.com/BenSheridanEdwards/ArchitectPlaybook)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-bensheridanedwards-architectplaybook-typescript-audit
- Seller: https://agentstack.voostack.com/s/bensheridanedwards
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
