<!-- Markdown mirror. Canonical: https://empryo.com/docs/tools/project -->

# Project tool

> The project tool reads your config files, runs the right lint, typecheck, test, build and format commands in 23 ecosystems, and checks code before commits.

The `project` tool lets the agent lint, typecheck, test, build and format your code without you telling it which commands your project uses. It reads the config files and lockfiles in the folder and picks the matching commands.

You do not call it yourself. The agent calls it when it needs to check its work. The results show up in the chat like any other tool call.

## Actions

| Action | What it runs | Example |
|--------|-------------|---------|
| `lint` | The project's linter | `project(action: "lint")` |
| `typecheck` | The type checker | `project(action: "typecheck")` |
| `test` | The test suite, or one test file | `project(action: "test", file: "src/foo.test.ts")` |
| `build` | The build | `project(action: "build")` |
| `format` | The formatter | `project(action: "format")` |
| `check` | Typecheck, lint and test at the same time, with one combined report | `project(action: "check")` |
| `run` | A dev server or a package script | `project(action: "run", script: "dev")` |
| `list` | The packages in a monorepo | `project(action: "list")` |

## Parameters

| Param | Required | What it does |
|-------|----------|--------------|
| `action` | yes | One of the actions above |
| `file` | no | Limit the run to one file (useful for `test` and `lint`) |
| `fix` | no | Let the linter fix what it can (for example `biome --write`, `eslint --fix`, `ruff --fix`) |
| `script` | no | The script to start with `run` |
| `flags` | no | Extra command-line flags |
| `env` | no | Environment variables for the command |
| `cwd` | no | The folder to run in, for one package of a monorepo |
| `timeout` | no | Time limit in milliseconds. By default it uses your `toolTimeout` setting (2 minutes unless you changed it) |

## How the toolchain is detected

Empryo looks for well-known files in the working folder:

```
bun.lock         -> bun test, bun run lint, bunx tsc --noEmit
Cargo.toml       -> cargo test, cargo clippy, cargo check
go.mod           -> go test, golangci-lint run, go build
pyproject.toml   -> pytest, ruff check, mypy/pyright
composer.json    -> phpunit, phpstan/psalm
Gemfile          -> rspec, rubocop
build.gradle     -> gradle test, gradle check
```

In a JavaScript or TypeScript project, a script in `package.json` (such as `lint`, `typecheck`, `test` or `build`) wins over the detected tool.

### JS/TS linter detection

When `package.json` has no `lint` script, Empryo picks the first linter it finds:

1. `biome.json` / `biome.jsonc` -> `biome check .`
2. `oxlintrc.json` / `.oxlintrc.json` -> `oxlint .`
3. `eslint.config.js` / `.eslintrc*` -> `eslint .`

### Python type checker detection

- If `pyrightconfig.json` exists, it runs `pyright`.
- Otherwise it runs `mypy .`.

When the project uses uv, Poetry or Pipenv (it has a `uv.lock`, `poetry.lock` or `Pipfile.lock`), the commands run through that tool, for example `uv run pytest`.

## Full ecosystem support

| Ecosystem | Lint | Typecheck | Test | Build |
|-----------|------|-----------|------|-------|
| **JS/TS (Bun)** | biome / oxlint / eslint | tsc | bun test | bun run build |
| **JS/TS (npm)** | biome / oxlint / eslint | tsc | npm test | npm run build |
| **JS/TS (pnpm)** | biome / oxlint / eslint | tsc | pnpm test | pnpm build |
| **JS/TS (Yarn)** | biome / oxlint / eslint | tsc | yarn test | yarn build |
| **Deno** | deno lint | deno check | deno test | -- |
| **Rust** | cargo clippy | cargo check | cargo test | cargo build |
| **Go** | golangci-lint / go vet | go build | go test | go build |
| **Python** | ruff / flake8 | pyright / mypy | pytest | -- |
| **PHP** | pint / php-cs-fixer | phpstan / psalm | phpunit | -- |
| **Ruby** | rubocop | -- | rspec / rails test | -- |
| **Swift** | swiftlint | swift build | swift test | swift build |
| **Elixir** | credo | dialyzer | mix test | mix compile |
| **Java/Kotlin (Gradle)** | gradle check (or spotless / ktlint) | gradle compileJava / compileKotlin | gradle test | gradle build |
| **C/C++ (CMake)** | clang-tidy | cmake build | ctest | cmake build |
| **Dart/Flutter** | dart analyze | dart analyze | flutter test | flutter build |
| **Zig** | zig fmt --check | zig build | zig build test | zig build |
| **Haskell** | hlint | stack build | stack test | stack build |
| **Scala** | scalafmt --check | sbt compile | sbt test | sbt compile |
| **.NET/C#** | dotnet format | dotnet build | dotnet test | dotnet build |
| **iOS/Xcode** | swiftlint | xcodebuild build | xcodebuild test | xcodebuild build |
| **Java (Maven)** | checkstyle | mvn compile | mvn test | mvn package |
| **C/C++ (Make)** | -- | -- | make test | make |
| **Clojure** | clj-kondo | -- | lein test / clj -M:test | lein uberjar |

A `--` means Empryo found no standard command for that step, so the action reports that nothing is configured.

## Pre-commit checks

When the agent commits with `git commit -m "…"` through the shell tool, Empryo first runs your linter and type checker. If they fail, the commit does not happen and the agent gets the errors so it can fix them.

What runs depends on the project:

- **JS/TS:** your linter and `tsc --noEmit`, for example `biome check . && tsc --noEmit`
- **Deno:** `deno lint && deno check .`
- **Rust:** `cargo clippy && cargo check`
- **Go:** `golangci-lint run && go build ./...` (or `go vet` when there is no golangci-lint config)
- **Python:** `ruff check` (or `flake8`) followed by `pyright` or `mypy`

This check does not depend on the agent remembering to lint. Empryo runs it on every agent commit.

The checks have a 15-second limit. Commits you make yourself, outside Empryo, are not affected.

> **Warning:**
> The pre-commit check only runs tools it recognizes from config files. It never runs scripts from `package.json`, so a commit cannot trigger an arbitrary script.

## Monorepo discovery

`project(action: "list")` finds the packages in a workspace.

### Supported workspace formats

- **pnpm:** `pnpm-workspace.yaml`
- **npm/yarn:** the `workspaces` field in `package.json`
- **Cargo:** `[workspace] members` in `Cargo.toml`
- **Go:** `use` directives in `go.work`

### Example output

```
5 packages:
  @myapp/web -- packages/web (biome) [lint, typecheck, test]
  @myapp/api -- packages/api (biome) [lint, typecheck, test]
  @myapp/shared -- packages/shared (biome) [lint, typecheck]
  @myapp/cli -- packages/cli (biome) [lint]
  @myapp/docs -- packages/docs [test]

Use project(action: "lint", cwd: "<path>") to target a specific package.
```

Each package is detected on its own, so the list shows what each one actually supports. When the agent tests a single file, Empryo runs the test from the package that owns that file instead of the whole workspace.

## When the agent uses the shell instead

If the agent runs a command like `bun run lint` or `cargo clippy` through the shell, the command still runs. The result adds a short reminder to use the project tool next time, because its output is structured and shows up properly in the UI.

```
Command succeeded. Next time use project(action: "lint") -- it auto-detects
the toolchain, results are structured, and output is visible in the UI.
```
