invar-cli 1.0.0

The Unchanging Key for Changing Data - Generate immutable structural state roots for cryptographic cache keys
# Invar

**The Unchanging Key for Changing Data**

A high-precision CLI tool for generating deterministic, content-addressable identifiers from any data source. Built for CI/CD pipelines, build systems, and content-addressed storage.

[![CI](https://github.com/swyrknt/invar-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/swyrknt/invar-cli/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE)

## Overview

Invar generates immutable structural state roots (Invar Keys) from arbitrary input data. Unlike traditional hashing, Invar processes data byte-by-byte through a mathematical synthesis engine, producing keys that represent the exact structural identity of your data.

### Key Properties

- **Deterministic**: Identical input always produces identical keys
- **Sensitive**: A single byte change produces a completely different key
- **Structural**: Keys represent the exact structural identity of data
- **Order-independent**: Directory hashing produces consistent keys regardless of file system ordering

## Installation

### Pre-built Binaries

Download from [GitHub Releases](https://github.com/swyrknt/invar-cli/releases):

```bash
# Linux (x86_64)
curl -LO https://github.com/swyrknt/invar-cli/releases/latest/download/invar-linux-x86_64.tar.gz
tar -xzf invar-linux-x86_64.tar.gz
sudo mv invar /usr/local/bin/

# macOS (Apple Silicon)
curl -LO https://github.com/swyrknt/invar-cli/releases/latest/download/invar-darwin-aarch64.tar.gz
tar -xzf invar-darwin-aarch64.tar.gz
sudo mv invar /usr/local/bin/
```

### Using Cargo

```bash
cargo install invar
```

### From Source

```bash
git clone https://github.com/swyrknt/invar-cli.git
cd invar-cli
cargo install --path .
```

### Docker

```bash
docker pull ghcr.io/swyrknt/invar-cli:latest

# Usage
echo "test" | docker run --rm -i ghcr.io/swyrknt/invar-cli gen
docker run --rm -v $(pwd):/data ghcr.io/swyrknt/invar-cli gen --directory /data/src
```

## Quick Start

```bash
# Generate key from a file
invar gen --file package-lock.json

# Generate key from stdin
echo "v1.0.1" | invar gen

# Generate key from inline data
invar gen --data "Hello, World!"

# Generate key from a directory
invar gen --directory ./src

# Verify data matches an expected key
invar check --file package-lock.json --expected <key>

# Watch for changes
invar watch ./src --recursive
```

## Features

### File and Data Hashing

```bash
# From file
invar gen --file config.json

# From stdin
cat data.bin | invar gen

# From inline string
invar gen --data "my-data-v1.0"

# JSON output with metadata
invar gen --file data.txt --output json
```

### Directory Hashing

Hash entire directories with deterministic, order-independent results:

```bash
# Hash a directory recursively
invar gen --directory ./src

# Include hidden files
invar gen --directory ./src --include-hidden

# JSON output with file count and size
invar gen --directory ./src --output json
# Output: {"key": "abc123...", "file_count": 42, "total_bytes": 123456}
```

### Ignore Patterns

Invar respects `.gitignore` and `.invarignore` files by default:

```bash
# Default behavior (respects .gitignore and .invarignore)
invar gen --directory ./src

# Disable .gitignore
invar gen --directory ./src --no-gitignore

# Disable .invarignore
invar gen --directory ./src --no-invarignore
```

Create a `.invarignore` file for invar-specific exclusions:

```
# .invarignore - patterns specific to invar
*.log
build/
dist/
*.mp4
```

### Streaming Mode

Process large files with constant memory usage:

```bash
# Stream large files
invar gen --file large-video.mp4 --stream

# With progress reporting (every N MB)
invar gen --file large-video.mp4 --stream --progress 100
```

### Watch Mode

Monitor files or directories for changes:

```bash
# Watch a single file
invar watch config.yaml

# Watch a directory recursively
invar watch ./src --recursive

# Custom debounce interval (milliseconds)
invar watch ./src --recursive --debounce 1000

# JSON output for scripting
invar watch ./src --recursive --output json
```

### Verification

```bash
# Generate a key
KEY=$(invar gen --file package-lock.json)

# Later, verify the file hasn't changed
invar check --file package-lock.json --expected "$KEY"

# Verify a directory
invar check --directory ./src --expected "$KEY"
```

## Use Cases

### CI/CD Cache Keys

```yaml
# GitHub Actions
- name: Generate cache key
  run: echo "CACHE_KEY=$(invar gen --directory src)" >> $GITHUB_ENV

- uses: actions/cache@v4
  with:
    key: build-${{ env.CACHE_KEY }}
```

### Build System Integration

```bash
# Skip rebuild if source unchanged
CURRENT=$(invar gen --directory src)
CACHED=$(cat .build-cache 2>/dev/null || echo "")

if [ "$CURRENT" = "$CACHED" ]; then
  echo "Source unchanged, skipping build"
else
  make build
  echo "$CURRENT" > .build-cache
fi
```

### Content-Addressable Storage

```bash
# Store files by content hash
KEY=$(invar gen --file document.pdf)
cp document.pdf "storage/$KEY.pdf"
```

### Configuration Versioning

```bash
# Detect configuration changes
if ! invar check --file config.yaml --expected "$STORED_KEY"; then
  echo "Configuration changed, reloading..."
  reload_configuration
fi
```

## Output Formats

### Text (Default)

```bash
$ invar gen --data "test"
a94a8fe5ccb19ba61c4c0873d391e987982fbbd3a1e3...
```

### JSON

```bash
$ invar gen --data "test" --output json
{
  "key": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3...",
  "length_bytes": 32
}

$ invar gen --directory ./src --output json
{
  "key": "abc123...",
  "file_count": 42,
  "total_bytes": 123456,
  "skipped": []
}
```

## Command Reference

### `invar gen` (alias: `g`)

Generate an Invar key from input data.

| Option | Short | Description |
|--------|-------|-------------|
| `--file <PATH>` | `-f` | Read from file |
| `--data <STRING>` | `-d` | Read from string |
| `--directory <PATH>` | | Hash directory recursively |
| `--stream` | | Use streaming mode (low memory) |
| `--progress <MB>` | | Report progress every N MB |
| `--output <FORMAT>` | `-o` | Output format: `text` or `json` |
| `--include-hidden` | | Include hidden files (directories) |
| `--follow-links` | | Follow symbolic links |
| `--no-gitignore` | | Ignore .gitignore patterns |
| `--no-invarignore` | | Ignore .invarignore patterns |

### `invar check` (alias: `c`)

Verify data produces an expected key.

| Option | Short | Description |
|--------|-------|-------------|
| `--expected <KEY>` | `-e` | Expected 64-character hex key |
| *(plus all gen options)* | | |

**Exit codes:** `0` = match, `1` = mismatch or error

### `invar watch` (alias: `w`)

Watch for file changes and report new keys.

| Option | Short | Description |
|--------|-------|-------------|
| `<PATH>` | | File or directory to watch |
| `--recursive` | `-r` | Watch subdirectories |
| `--debounce <MS>` | | Debounce interval (default: 500) |
| `--output <FORMAT>` | `-o` | Output format: `text` or `json` |

## How It Works

Invar uses the [Distinction Calculus](https://crates.io/crates/koru-lambda-core) to generate structural identifiers:

1. Initialize the root Distinction (D0)
2. Process each byte of input data
3. Convert each byte to a canonical Distinction via binary path encoding
4. Synthesize with the running root: `D_root = synthesize(D_root, D_byte)`
5. Return the final Distinction ID as a 64-character hex string

This ensures:
- **Byte-level precision**: Every byte affects the final key
- **Structural uniqueness**: Different data produces different keys
- **Mathematical determinism**: Same data always produces same key

## Examples

See the [examples](examples/) directory for:
- GitHub Actions cache workflows
- GitLab CI pipelines
- Docker build optimization
- Monorepo selective testing

## Development

```bash
# Build
cargo build --release

# Test (87 tests)
cargo test

# Lint
cargo clippy -- -D warnings

# Format
cargo fmt
```

## License

Dual-licensed under [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE) at your option.

## Acknowledgments

- Built on [koru-lambda-core]https://crates.io/crates/koru-lambda-core
- Named after [Invar]https://en.wikipedia.org/wiki/Invar, an alloy with minimal thermal expansion