jan-cli 0.20.0

YAML-defined CLI trees with progressive help, optional exec aliases, merged extra specs, and SQLite audit logging keyed by git branch
Documentation
# Packages

`packages:` declares **package-manager** dependencies. Distinct from `dependencies:`, which names other jan scripts.

Identical declarations share a cached environment under `~/.cache/jan/packages/<manager>/<hash>/` (or `$JAN_CACHE_DIR/…`). Successful runs update `.jan-used` so `jan packages prune` can keep hot caches.

Package dependencies must pin an **exact** version so the env hash identifies a single resolution. Warm hits skip spawning the manager `--version`; the tool is only required when an env must be built.

Inspect caches with [`jan packages`](../cli/packages.md).

## uv (Python)

Inline pins need `==` or `===`. Requirements files are checked line by line (no `-r` / `-e`). `project:` requires `pyproject.toml` plus `uv.lock`. Locked projects install from the exported lock plus `uv pip install --no-deps <project>`.

```yaml
# Inline pins
analyze:
  packages:
    uv: [rich==13.9.4, pathspec==0.12.1]
  commands:
    run:
      exec:
        python: |
          import rich
          print("ok")

# Requirements file under the jan use root
ingest:
  packages:
    uv:
      requirements: deps/ingest.txt
  exec:
    argv: ["python3", "tools/ingest.py"]

# uv project directory
train:
  packages:
    uv:
      project: deps/ml
      python: ">=3.11"
  exec:
    python: -m train
```

Map form must set exactly one of `packages` (alias `deps`), `project`, or `requirements`. Optional `python:` is a minimum like `3.11` or `>=3.11`, checked against the host/venv toolchain and included in the env hash.

On run, jan requires `uv` on PATH (cold build), ensures the hashed venv, prepends its `bin/` to the child PATH, and resolves `python` / `python3` from that venv.

Worked example: [`examples/packages.spec.yaml`](../workflows/examples.md) — `jan --cwd examples summarize-sales run`.

## pnpm (Node)

Inline entries need a full `name@major.minor.patch` (no ranges, dist-tags, aliases, or git/file specs). `project:` requires `package.json` + `pnpm-lock.yaml`, installed with `--frozen-lockfile`. Envs are installed in the cache (never in your tree).

```yaml
pin-report:
  packages:
    pnpm:
      - lodash@4.17.21
      - cowsay@1.6.0
      # or map form:
      # packages: [lodash@4.17.21]
      # node: ">=18"
  commands:
    run:
      exec:
        node: |
          console.log(require("lodash").VERSION)
    cow:
      exec:
        argv: ["cowsay", "pinned deps only"]
```

On run jan prepends `node_modules/.bin` to PATH, sets `NODE_PATH`, and resolves argv[0] from `.bin`.

## gradle (JVM / Kotlin)

Inline entries are `group:artifact:version` with an exact version (no `+`, Ivy ranges, or `latest.release`). `project:` needs `build.gradle` or `build.gradle.kts` plus `gradle.lockfile`.

```yaml
kotlin-greet:
  packages:
    gradle:
      - org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6
      # java: 21   # alias: jdk
  commands:
    run:
      exec:
        kotlin: packages/kotlin-greet.kts
```

Gradle resolves jars into `lib/` (Java Library plugin / `runtimeClasspath`). jan injects `CLASSPATH` and automatic `-cp` for `java` / `kotlin` / `kotlinc` argv. Cold resolves stream Gradle's console output and print progress on stderr.

## Runtime minima

| Manager | Field | Example |
|---------|-------|---------|
| uv | `python` | `3.11`, `>=3.11` |
| pnpm | `node` | `18`, `>=18.0.0` |
| gradle | `java` (alias `jdk`) | `21`, `>=21` |

Constraints are checked on ensure/run and included in the env hash.

## Chain merge

Deeper nodes **replace** a given manager's declaration (no list merge). A `help` leaf under a node that declares packages still inherits that declaration for `jan packages` listing.