# Find Dead Code in an Angular App

Yes. An agent lists the unused files, exports, components and dependencies in your Angular app, proving each by deleting it and running the build and Vitest.

[Run this on Rush](https://cloud.getrush.ai/run/find-dead-code/angular/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and reads how the app is wired before it trusts any tool. In an Angular app like realworld-apps/angular-realworld-example-app, `package.json` pins `@angular/core` `21.2.12`, `@rx-angular/cdk` and `@rx-angular/template` `21.0.0`, `marked` `^17.0.1` and `rxjs` `^7.8.2`, and the scripts are `build` (`ng build`), `test` (`vitest`), `test:coverage`, `test:e2e` (Playwright, with `@security` tests split out) and `format:check` (Prettier). The lockfile is `bun.lock`, the only workflow that checks code is `.github/workflows/lint.yml`, which runs `bun run format:check`, and `README.md` says to run `bun run setup`, which initialises the `realworld` git submodule that `angular.json` reads `realworld/assets/theme/styles.css` from. So the agent runs `git submodule update --init` and `bun install` first, or `ng build` fails on a missing stylesheet before any analysis starts.

Then it maps the entry points, because that is where dead-code tools go wrong on Angular. `angular.json` has one project whose `build` target starts at `src/main.ts` with `tsconfig.app.json`, and `src/app/app.routes.ts` loads every page lazily with `loadComponent` and `loadChildren`: home, `tag/:tag`, login, register, settings, `profile` (through `features/profile/profile.routes.ts`), `editor`, `editor/:slug` and `article/:slug`. Those dynamic imports are reachable code even though nothing imports them statically. `vitest.config.ts` includes `src/**/*.spec.ts`, sets `src/test-setup.ts` as its setup file and aliases `@` to `src`, and the six spec files all test services: articles, comments, tags, profile, user and jwt. The agent configures knip with its Angular plugin so `angular.json`, the route files, the Vitest setup and the specs count as entries, and excludes the `realworld` submodule, which is the shared spec and theme, not app code.

The candidates come from three places and each one is checked, not reported on faith. For files and exports, the agent walks `src/app`: `core/models/loading-state.model.ts` and `core/models/errors.model.ts`, the three interceptors in `core/interceptors`, the `if-authenticated.directive.ts`, the `default-image.pipe.ts` and `markdown.pipe.ts` under `shared/pipes`, the `article-list-config.model.ts` and the `favorite-button` and `follow-button` components. An Angular template can reference a standalone component or pipe only through the `imports` array of the component that uses it, so a pipe listed in no `imports` array is dead even if a file imports its class. For dependencies, it checks whether `@angular/animations` is injected anywhere, whether `@angular/platform-browser-dynamic` is used outside `src/test-setup.ts` and the specs (which would make it a dev dependency), and whether `@types/marked` still adds anything now that `marked` ships its own types; `tslib` stays because `tsconfig.json` sets `importHelpers`.

Each suspect is removed on a scratch branch and the agent runs `bun run build` and `bun run test`. The build matters more than usual here: `tsconfig.json` sets `strict` and `angularCompilerOptions.strictTemplates`, so a template that still uses a removed component or pipe fails ahead-of-time compilation with the template file and line. A candidate goes into the report only when both commands pass without it; a candidate that fails goes into a separate list with the error, which is usually a template reference knip could not see. The agent pushes nothing and opens no pull request; it writes the report as `dead-code.md`.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Find dead code in this Angular app and write dead-code.md. Run git submodule update --init and bun install first, since angular.json reads styles from the realworld submodule. Read angular.json, tsconfig.app.json, vitest.config.ts and src/app/app.routes.ts, then configure knip with its Angular plugin so src/main.ts, every loadComponent and loadChildren route, src/test-setup.ts and the spec files are entry points, and exclude the realworld directory. List candidate files, exported symbols, standalone components and pipes that appear in no imports array, and package dependencies that nothing imports. Check every candidate by removing it on a scratch branch and running bun run build and bun run test; the build uses strictTemplates, so treat an AOT template error as proof the candidate is used. Write dead-code.md with sections for files, exports, components and pipes, and dependencies, each entry with its path and the command output that passed without it, and a final section listing every candidate that failed the check with the error. Do not commit, push or open a pull request.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is one file, `dead-code.md`, as an artifact on the session. Reading the session returns a download link for it that works for an hour, and reading again returns a fresh link. The report has four sections of things that are safe to delete: source files nothing reaches from `src/main.ts` or a lazy route, exported symbols nothing imports, standalone components and pipes that appear in no `imports` array, and entries in `package.json` that nothing imports or that only the test setup imports. Every line names the path, the `ng build` and `vitest` runs that passed with it gone, and for a dependency, whether it should move to `devDependencies` rather than be removed.

A fifth section lists the suspects that failed the check with the error each one produced. On an Angular app the usual entry there is a component or pipe that no TypeScript file imports but a template uses through an `imports` array, which the strict template compiler reports with the template path. That section is where the knip configuration needs another entry point or an ignore, and the report says which.

Before acting on it, a reviewer checks that the entry points match how the app is actually served, since a route added only in a feature branch would make its component look dead on the default branch, and that the Playwright specs under the submodule did not reference a removed debug hook such as `window.__conduit_debug__`, which `src/app/app.config.ts` sets for the end-to-end tests and which no unit test touches.

## Run it on every first Monday of the month

Dead code builds up a route at a time, so one report a month is enough. A cron job on your server sends this request on the first Monday of each month with a `request_id` made from the date, so a retried request returns the run it already started instead of starting another. Each run begins from a fresh clone, so the report describes the default branch as it is that morning, submodule included.

Give the key a webhook URL and Rush POSTs `run.completed` when the turn finishes, with `data.session_url`. Your handler reads the session from that URL, takes the `dead-code.md` link from its artifacts and files it as an issue or posts it to the channel where the team plans cleanup.

## Why run it on Rush?

Proving an Angular file is dead means deleting it, rebuilding with the strict template compiler and running the suite, once per suspect. On a laptop that is an afternoon of stashing and waiting on `ng build`, with the risk of leaving a half-deleted working tree behind. In a Rush sandbox the agent does every delete-and-rebuild cycle on its own clone with its own `node_modules` and submodule checkout, and nothing it removes touches your branches.

The report is only as good as its evidence, and the evidence is kept. The transcript at `GET /o/{org}/sessions/{id}/messages` holds every `ng build` and `vitest` invocation with its output, including the template errors that saved a false positive, so a reviewer can see why each line is in the report without re-running anything. Several apps, or several branches of one app, run at once in their own sandboxes.

## Frequently asked questions

### Will it count lazily loaded routes as dead?

No. The agent reads `src/app/app.routes.ts` and `features/profile/profile.routes.ts` and registers every `loadComponent` and `loadChildren` target as an entry point before knip runs. A page that only a route reaches is live, and the build check would catch a mistake anyway, because `ng build` compiles every route target.

### What about components and pipes that only templates use?

A standalone component or pipe reaches a template only through the `imports` array of the component that uses it, so the agent checks those arrays, not only TypeScript imports. If a candidate is used in a template the agent missed, `strictTemplates` fails the build with the template file and line, and the candidate lands in the failed section instead of the safe list.

### Does it need the realworld submodule?

Yes, to build. `angular.json` lists `realworld/assets/theme/styles.css` under `styles` and `playwright.config.ts` points `testDir` at `realworld/specs/e2e`, so the agent initialises the submodule before the first build and excludes it from the analysis, since it is shared RealWorld material rather than app code.

### Will it remove anything or open a pull request?

Not with this prompt. The deletions happen on a scratch branch inside the sandbox only to run the checks, and the report is the result. Send a follow-up to the same session asking it to delete the items in the safe list and open a pull request, and it picks up the same clone with the report already in hand.

### Can it also report dependencies that belong in devDependencies?

It does. A package that only `src/test-setup.ts` or the spec files import, which is what `@angular/platform-browser-dynamic` looks like at first glance, is listed as a move rather than a removal, with the files that import it, and the same check is run after the move.
