> ## Documentation Index
> Fetch the complete documentation index at: https://tashfiq.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# AE-ENV-001

> Toolchain, lockfiles, and hooks should make the repo reproducible.

**Rule ID:** AE-ENV-001 · **Severity:** <Badge color="yellow">Medium</Badge> · **Category:** Environment · **Auto-fixable:** No

## Why this rule

An agent that cannot reproduce the project's toolchain installs the wrong version, runs tests against the wrong runtime, and produces fixes that pass locally but break in CI or on a colleague's machine.

Reproducibility is not just a developer convenience — it is the foundation of an agent's ability to self-verify. Without a pinned, committed toolchain, the agent's environment is undefined. A fix that works in one environment may silently fail in another.

## What triggers it

Charter checks three things independently. Any one of them can fire the finding:

**1. Missing toolchain declaration** — No pinned runtime file exists for an active language.

**2. Missing lockfile** — A language that produces a lockfile has not committed one.

**3. Missing hook manager config** — No hook manager configuration is committed to the repo, so pre-commit enforcement is local-only and cannot be verified by an agent or reproduced in CI.

<AccordionGroup>
  <Accordion title="Accepted toolchain declarations">
    Charter recognizes the following files as valid toolchain declarations:

    | Scope                   | Accepted files                                                                |
    | ----------------------- | ----------------------------------------------------------------------------- |
    | Universal               | `mise.toml`, `.mise.toml`, `.tool-versions`, `devcontainer.json`, `flake.nix` |
    | Go                      | `go.mod` with a `toolchain` directive                                         |
    | JavaScript / TypeScript | `.nvmrc`, `.node-version`, `bunfig.toml`, `package.json#volta`                |
    | Python                  | `pyproject.toml` with `requires-python`                                       |
    | Rust                    | `rust-toolchain.toml`                                                         |
    | Swift                   | `.swift-version`                                                              |
    | Kotlin / JVM            | `gradle-wrapper.properties`                                                   |
    | Ruby                    | `.ruby-version`                                                               |
  </Accordion>

  <Accordion title="Accepted lockfiles">
    | Language          | Lockfile                                      |
    | ----------------- | --------------------------------------------- |
    | Go                | `go.sum`                                      |
    | JavaScript (npm)  | `package-lock.json`                           |
    | JavaScript (yarn) | `yarn.lock`                                   |
    | JavaScript (pnpm) | `pnpm-lock.yaml`                              |
    | Rust              | `Cargo.lock`                                  |
    | Python (uv)       | `uv.lock`                                     |
    | Python (pip)      | `requirements.txt` pinned with exact versions |
  </Accordion>

  <Accordion title="Accepted hook manager configs">
    | Hook manager | Config file               |
    | ------------ | ------------------------- |
    | hk           | `hk.pkl`                  |
    | husky        | Any file under `.husky/`  |
    | lefthook     | `lefthook.yml`            |
    | pre-commit   | `.pre-commit-config.yaml` |
  </Accordion>
</AccordionGroup>

## Examples

<Tabs>
  <Tab title="Failing">
    A Node.js repo with a `package.json` but no pinned runtime and no committed lockfile — the runtime is floating and the dependency graph is not reproducible:

    ```text repo tree theme={null}
    my-app/
    ├── package.json
    ├── src/
    │   └── index.ts
    # no .nvmrc / engines field → no toolchain declaration
    # no package-lock.json      → no lockfile
    # no hk.pkl / .husky/        → no hook manager config
    ```

    A Go repo with `go.mod` but no `go.sum` committed fires on the missing lockfile alone:

    ```text repo tree theme={null}
    my-service/
    ├── go.mod
    └── main.go
    # no go.sum → lockfile missing
    ```
  </Tab>

  <Tab title="Passing">
    A repo with `mise.toml` pinning Go and Node versions, `go.sum` and `package-lock.json` committed, and `hk.pkl` declaring pre-commit hooks:

    ```toml mise.toml theme={null}
    [tools]
    go   = "1.26.3"
    node = "22.0.0"
    ```

    ```toml mise.lock theme={null}
    # generated — committed to repo
    ```

    ```pkl hk.pkl theme={null}
    hooks {
      preCommit = ["moon run :lint", "moon run :docs"]
      prePush   = ["moon run :test", "moon run :security"]
    }
    ```
  </Tab>
</Tabs>

## How to fix

<Tabs>
  <Tab title="mise (recommended)">
    `mise` is a universal version manager that covers every language in a single file:

    ```toml mise.toml theme={null}
    [tools]
    go     = "1.26.3"
    node   = "22.0.0"
    python = "3.12.4"
    ```

    Run `mise install` to generate `mise.lock`, then commit both files. Add `mise install` to your onboarding docs.
  </Tab>

  <Tab title="Language-native">
    Use the language's own toolchain file if you prefer not to add `mise`:

    ```text .nvmrc (Node.js) theme={null}
    22.0.0
    ```

    ```toml rust-toolchain.toml (Rust) theme={null}
    [toolchain]
    channel = "1.79.0"
    ```

    ```text .python-version (Python via pyenv) theme={null}
    3.12.4
    ```

    Commit the toolchain file and the corresponding lockfile together.
  </Tab>
</Tabs>

## Score impact

`Medium` (−4 per finding). No hard cap — caps are reserved for raw-secret and Blocker findings. Each sub-check (toolchain, lockfile, hook config) is a separate finding instance, so a repo missing all three can incur −12 points from this rule alone. See [Scoring and caps](/docs/concepts/scoring-and-caps).

## Edge cases

<AccordionGroup>
  <Accordion title="Partial language coverage">
    Partial language coverage is acceptable only if the uncovered language is not active in the repo. See AE-TEST-001 for how Charter determines whether a language is active.
  </Accordion>

  <Accordion title="Local-only helper scripts">
    Local-only helper scripts do not satisfy reproducibility for tracked task config. The toolchain declaration and lockfile must be committed to the repo.
  </Accordion>
</AccordionGroup>

## Related rules

<CardGroup cols={2}>
  <Card title="AE-CI-002" icon="circle-check" href="/rules/AE-CI-002">
    Requires Charter to run in CI so the reproducible environment is verified on every PR.
  </Card>

  <Card title="AE-AUTO-001" icon="player-play" href="/rules/AE-AUTO-001">
    Requires a discoverable test command — meaningless without a reproducible runtime.
  </Card>
</CardGroup>

## CLI

```bash theme={null}
charter explain AE-ENV-001
```
