Metadata-Version: 2.4
Name: locci
Version: 0.1.1
Summary: A fast, local-first CI engine.
Author: Locci Developers
License-File: LICENSE
Requires-Python: >=3.9
Requires-Dist: networkx>=3.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: pyyaml>=6.0.1
Requires-Dist: rich>=13.0.0
Requires-Dist: tomli>=2.0.1; python_version < '3.11'
Requires-Dist: typer>=0.9.0
Requires-Dist: watchdog>=3.0.0
Provides-Extra: ai
Requires-Dist: anthropic>=0.20.0; extra == 'ai'
Provides-Extra: dev
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Provides-Extra: s3
Requires-Dist: boto3>=1.28.0; extra == 's3'
Description-Content-Type: text/markdown

# Locci — Local CI

[![PyPI version](https://img.shields.io/pypi/v/locci)](https://pypi.org/project/locci/)
[![Python](https://img.shields.io/pypi/pyversions/locci)](https://pypi.org/project/locci/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

**Locci** runs the safe parts of your CI pipeline locally — lint, tests, builds — before you push. It auto-detects your project stack, reads your GitHub Actions workflows, executes tasks through an embedded DAG engine with content-hash caching, and tells you exactly what it skipped and why.

> **Locci does not replace your remote CI runner.** It catches the fast, side-effect-free failures locally so you waste fewer CI minutes on trivial errors.

---

## Table of Contents

- [Requirements](#requirements)
- [Installation](#installation)
- [Quick start](#quick-start)
- [Commands](#commands)
  - [setup](#locci-setup)
  - [check](#locci-check)
  - [run](#locci-run)
  - [preflight](#locci-preflight)
  - [ready](#locci-ready)
  - [watch](#locci-watch)
  - [fix](#locci-fix)
  - [doctor](#locci-doctor)
  - [graph](#locci-graph)
  - [logs](#locci-logs)
  - [cache-clear](#locci-cache-clear)
  - [init](#locci-init)
  - [export](#locci-export)
  - [hook](#locci-hook)
- [Configuration reference — locci.yml](#configuration-reference)
- [Caching](#caching)
- [Secrets](#secrets)
- [Remote cache (S3)](#remote-cache-s3)
- [GitHub Actions integration](#github-actions-integration)
- [Exit codes](#exit-codes)
- [Security caveats](#security-caveats)

---

## Requirements

- Python **3.9+**
- Git (for `preflight`, `--since`)
- Any language toolchains for stacks you want to run (`node`, `go`, `cargo`, `pytest`, etc.)

---

## Installation

### Recommended — pipx (isolated, globally available)

```bash
pipx install locci
```

This puts `locci` on your PATH without polluting any project virtualenv.

### pip

```bash
pip install locci
```

### With optional extras

```bash
pip install "locci[ai]"   # locci fix — AI-powered failure diagnosis (requires Anthropic key)
pip install "locci[s3]"   # locci check --remote-cache s3://...
```

---

## Quick start

```bash
cd my-project

# 1. Generate locci.yml and check your environment
locci setup

# 2. Run all safe local checks
locci check

# 3. Before every push — run only tasks affected by your changes
locci preflight
```

---

## Commands

### `locci setup`

Detects your project stack, generates a `locci.yml`, runs `locci doctor`, and optionally installs the git pre-push hook.

```bash
locci setup                      # interactive — writes locci.yml, prompts for hook install
locci setup --preview            # print generated locci.yml to stdout, do not write anything
locci setup --force              # back up existing locci.yml and replace it
locci setup --install-hook       # install hook non-interactively
locci setup --no-install-hook    # skip hook install non-interactively
```

---

### `locci check`

Runs all detected local-safe tasks. This is the main command.

```bash
locci check                          # run everything in the current directory
locci check /path/to/project         # run from a specific path
locci check --dry-run                # show the full task list without running anything
locci check --verbose                # stream full stdout/stderr for every task
locci check --no-cache               # ignore cached results, re-run everything
locci check --jobs 8                 # run up to 8 tasks in parallel (default: 4)
locci check --since main             # run only tasks affected by changes since `main`
locci check --since HEAD~3           # affected by the last 3 commits
locci check --scope gha              # run only tasks whose name starts with "gha"
locci check --matrix python=3.9,3.11 # expand tasks across a matrix of env variable values
locci check --allow-unsafe           # run tasks Locci would normally skip as unsafe
locci check --strict-coverage        # exit 2 when any tasks were skipped (coverage gap)
locci check --remote-cache s3://my-bucket/prefix  # pull/push cache from S3
```

**How tasks are selected:**
1. Auto-detected from your repo (GitHub Actions workflows, `pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, …).
2. Filtered by `--scope` (prefix match on task name).
3. Filtered by `--since` (only tasks whose `input_globs` overlap changed files).
4. Safety policy applied — unsafe tasks are skipped unless `--allow-unsafe` is set.

---

### `locci run`

Backward-compatible alias for `locci check`. Accepts the same flags.

```bash
locci run
locci run --verbose --no-cache
```

---

### `locci preflight`

Automation-facing affected check. Designed to be called from a git pre-push hook or a script. Never prompts.

```bash
locci preflight          # detects base ref automatically (upstream branch or `main`)
locci preflight --strict-coverage
locci preflight --allow-unsafe
```

Locci resolves the base ref in this order:

1. `LOCCI_OLD_SHA` environment variable (set by the git hook).
2. `merge-base` of `HEAD` and the tracked upstream branch.
3. Falls back to `main`.

---

### `locci ready`

Full pre-push confidence check. Same as `check` but always enforces `--strict-coverage`. Exits 2 if any tasks were skipped due to coverage gaps.

```bash
locci ready
locci ready --verbose
locci ready --allow-unsafe
```

---

### `locci watch`

Watches for file changes and re-runs only the affected tasks on every save. Useful for tight feedback loops during development.

```bash
locci watch
locci watch /path/to/project
```

Press **Ctrl-C** to stop. Requires `watchdog` (bundled as a dependency).

---

### `locci fix`

AI-powered diagnosis of the most recent failed task. Reads the failure from the local cache and explains what went wrong.

```bash
locci fix            # diagnose the latest failure
locci fix --flaky    # list tasks that have both passed and failed recently
```

Requires the `[ai]` extra: `pip install "locci[ai]"` and the `ANTHROPIC_API_KEY` environment variable.

---

### `locci doctor`

Checks that all tools required by your project are installed and on `PATH`. Prints a status table with fix hints for anything missing.

```bash
locci doctor
locci doctor /path/to/project
```

**Detected stacks and their required tools:**

| Stack | Tools checked |
|---|---|
| Python | `python3`, `pip`, `pytest`, `ruff` (optional) |
| Node.js | `node`, `npm` |
| Go | `go` |
| Rust | `cargo` |
| Terraform | `terraform` |
| Protobuf | `buf` |
| Docker | `docker` (optional) |

Exit code 0 if all required tools are found, 3 if any required tool is missing.

---

### `locci graph`

Prints the full task dependency graph (DAG) without running anything.

```bash
locci graph
locci graph /path/to/project
```

Output shows each task name, its command, its dependencies, and skip status.

---

### `locci logs`

View stdout/stderr stored in the local task cache.

```bash
locci logs my-task-name    # view the last run of a specific task
locci logs --failed        # view the last failed task
```

---

### `locci cache-clear`

Deletes all cached task results from `~/.locci/cache.db`.

```bash
locci cache-clear
```

---

### `locci init`

Generates a `locci.yml` from auto-detection or by importing from GitHub Actions workflows. Lower-level alternative to `locci setup`.

```bash
locci init                  # generate from auto-detected stack
locci init --from-github    # import tasks from .github/workflows/*.yml
```

Exits 1 if `locci.yml` already exists — use `locci setup --force` to overwrite.

---

### `locci export`

Exports the Locci pipeline to a native CI provider workflow file.

```bash
locci export                      # exports to .github/workflows/locci-generated.yml
locci export --format github      # explicit GitHub Actions format (currently the only supported format)
```

The generated workflow uses the literal `${{ secrets.X }}` expressions — no secret values are embedded in the output.

---

### `locci hook`

Manages the Locci git pre-push hook.

```bash
locci hook install      # install .git/hooks/pre-push that calls `locci preflight`
locci hook uninstall    # remove the hook
locci hook status       # print: installed | not-installed | foreign
```

The hook sets `LOCCI_OLD_SHA` so `locci preflight` can compute the exact changed ref range.

---

## Configuration reference

Locci works **without any config** — it auto-detects tasks from your project. `locci.yml` (or `locci.yaml`) is optional and lets you tune, override, or add tasks.

```yaml
version: 1

# --- Optional: override safety policy ---
safety:
  skip_unsafe: true                             # default: true
  unsafe_keywords:                              # tasks containing these words are skipped
    - deploy
    - publish
    - release
    - production
    - prod
    - upload

# --- Optional: caching ---
cache:
  enabled: true                                 # default: true

# --- Optional: add custom tasks ---
tasks:
  my-lint:
    run: ["ruff", "check", "."]               # "run" and "cmd" are aliases
    input_globs: ["**/*.py"]
    timeout: 120                               # seconds; null = no timeout

  my-integration-test:
    run: ["pytest", "tests/integration/"]
    needs: ["my-lint"]                         # "needs" and "depends_on" are aliases
    env:
      DATABASE_URL: "sqlite:///test.db"
    required_secrets: ["API_KEY"]              # resolved from .env or shell env
    output_dirs: [".pytest_cache"]
    cwd: "."
    local_safe: true                           # force-mark as safe (bypass keyword check)

# --- Optional: override auto-detected tasks ---
overrides:
  gha:build:
    run: ["make", "build"]
    timeout: 300
```

### Task fields

| Field | Type | Default | Description |
|---|---|---|---|
| `run` / `cmd` | `list[str]` | — | Command to execute (required) |
| `needs` / `depends_on` | `list[str]` | `[]` | Task names that must succeed first |
| `input_globs` | `list[str]` | `["**/*"]` | Files that invalidate the cache when changed |
| `env` | `dict` | `{}` | Extra environment variables for this task |
| `required_secrets` | `list[str]` | `[]` | Secret names to resolve from `.env` or environment |
| `output_dirs` | `list[str]` | `[]` | Output directories included in the cache key |
| `cwd` | `str` | `"."` | Working directory relative to project root |
| `timeout` | `int \| null` | `null` | Timeout in seconds |
| `local_safe` | `bool \| null` | `null` | Explicitly mark safe (`true`) or unsafe (`false`) |

---

## Caching

Locci caches task results in `~/.locci/cache.db` (SQLite).

**Cache key** = SHA-256 of:
- The task command
- Contents of all files matched by `input_globs`
- Contents of all directories in `output_dirs`
- Extra environment variables (`env` + resolved secrets)

A task is **cache-hit** if the key matches the last successful run. It is then skipped and shown as `cached` in the summary.

```bash
locci check --no-cache      # bypass cache, re-run everything
locci cache-clear           # wipe the cache database
```

---

## Secrets

Locci resolves secrets from your local `.env` file (project root) or shell environment.

Mark which secrets a task needs in `locci.yml`:

```yaml
tasks:
  my-task:
    run: ["pytest", "tests/"]
    required_secrets: ["STRIPE_API_KEY", "DATABASE_URL"]
```

At runtime, Locci reads `.env`, injects matched values into the task's environment, and **masks them from all log output**. If a secret is not found, the task is skipped with the category `secret-required`.

For GitHub Actions workflows, `${{ secrets.FOO }}` expressions are automatically detected and translated to the corresponding `required_secrets` entry.

---

## Remote cache (S3)

Share the task cache across machines or CI runners.

```bash
pip install "locci[s3]"

locci check --remote-cache s3://my-bucket/locci-cache
```

On cache-miss, Locci falls back to running locally and then pushes the result. On cache-hit, it pulls and restores without running.

> **⚠️ Security:** Remote cache artifacts are not cryptographically signed. Only use shared S3 caches with teams you trust. See [Security caveats](#security-caveats).

---

## GitHub Actions integration

Locci reads `.github/workflows/*.yml` automatically — no config needed. It translates each job step into a local task with the following logic:

- **`run:` steps** → executed as shell commands.
- **`uses:` steps** (known setup actions) → translated to local equivalents:

| Action | Local equivalent |
|---|---|
| `actions/setup-python` | `python3 -m pip install --upgrade pip` |
| `actions/setup-node` | `node --version` (checks node is present) |
| `actions/setup-go` | `go version` |
| `actions/setup-java` | `java -version` |
| `actions/setup-dotnet` | `dotnet --version` |
| `ruby/setup-ruby` | `ruby --version` |
| `dtolnay/rust-toolchain` | `cargo --version` |
| `actions/cache` | no-op (Locci has its own cache) |
| `actions/checkout` | no-op (repo is already checked out) |
| Any unknown `uses:` | no-op (silently skipped) |

- **Dangerous deploy/publish actions** (ECS deploy, Terraform apply, PyPI publish, etc.) → blocked as `unsafe`.

**Matrix jobs:** Only the first matrix leg runs locally. Additional legs are reported as `remote-only` in the summary.

**Conditional steps:** `if:` expressions are evaluated. `github.event_name` is treated as `push` during local runs.

---

## Exit codes

| Code | Meaning |
|---|---|
| `0` | All runnable tasks passed |
| `1` | One or more runnable tasks failed |
| `2` | All runnable tasks passed, but `--strict-coverage` was set and tasks were skipped |
| `3` | Required host tools are missing (`locci doctor` for details) |
| `4` | Invalid or missing config / bad CLI arguments |

---

## Security caveats

**Local safety heuristics are best-effort.**
Locci identifies unsafe tasks using a keyword blocklist (`deploy`, `publish`, `release`, `production`, `prod`, `upload`). This is not a security boundary. A task that modifies production state without using these keywords will still execute locally. Use `local_safe: false` in `locci.yml` to explicitly block a task.

**Remote cache supply chain risk.**
When using `--remote-cache s3://...`, Locci pulls cache artifacts directly into your workspace. There is no cryptographic signature or authenticity verification on these artifacts. Anyone with write access to the S3 bucket could plant a malicious payload or falsify a test outcome. Only use shared remote caching with trusted teams.
