# Contributing to StackAI CLI

This guide covers the local development workflow for the StackAI CLI. You do **not** need to publish or install the CLI to test changes.

## Table of Contents

* [Prerequisites](#prerequisites)
* [Quick Start](#quick-start)
* [Using Cargo Aliases](#using-cargo-aliases)
* [Local Development Workflow](#local-development-workflow)
* [Testing Strategy](#testing-strategy)
* [Code Quality Checks](#code-quality-checks)
* [Safe Testing (Isolated Environment)](#safe-testing-isolated-environment)
* [Adding a New Command](#adding-a-new-command)
* [Pull Request Workflow](#pull-request-workflow)
* [Debugging Tips](#debugging-tips)
* [Build Performance](#build-performance)
* [Common Issues](#common-issues)

***

## Prerequisites

### Required Tools

| Tool            | Version | Installation                                                      |
| --------------- | ------- | ----------------------------------------------------------------- |
| **Rust**        | 1.85.0+ | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh` |
| **Docker**      | 24.0+   | [Install Docker](https://docs.docker.com/get-docker/)             |
| **cargo-audit** | Latest  | `cargo install cargo-audit --locked`                              |
| **cargo-deny**  | Latest  | `cargo install cargo-deny --locked`                               |

### Recommended Tools

| Tool              | Purpose                                        | Installation                  |
| ----------------- | ---------------------------------------------- | ----------------------------- |
| **bacon**         | Background code checker (replaces cargo-watch) | `cargo install bacon`         |
| **cargo-nextest** | Faster test runner (3x faster)                 | `cargo install cargo-nextest` |
| **cargo-machete** | Dead code detection                            | `cargo install cargo-machete` |

> ⚠️ **Note**: `cargo-watch` was archived in January 2025. Use **bacon** instead for file watching.

***

## Quick Start

```bash
# Clone the repository
git clone https://github.com/stack-ai/stackai-onprem-cli
cd stackai-onprem-cli

# Build and run (debug mode)
cargo run -- --help

# Run a specific command
cargo run -- deploy status
cargo run -- diagnose doctor

# Use project aliases (see next section)
cargo lint      # Run clippy
cargo format    # Check formatting
cargo test-all  # Run all tests with output
```

***

## Using Cargo Aliases

The project defines convenient aliases in `.cargo/config.toml`. **Use these instead of typing full commands**:

### Quality Checks

```bash
cargo format      # Check formatting (cargo fmt --check)
cargo fformat     # Fix formatting (cargo fmt)
cargo lint        # Run clippy with warnings as errors
cargo lint-strict # Run clippy with pedantic/nursery lints
```

### Testing

```bash
cargo test-all     # Run all tests with output (--nocapture)
cargo test-verbose # Run tests single-threaded with output
cargo test-doc     # Run documentation tests only
```

### CI Commands

```bash
cargo ci-check    # cargo check --all-features --all-targets
cargo ci-lint     # Same as cargo lint
cargo ci-fmt      # Same as cargo format
cargo ci-test     # cargo test --all-features
cargo ci-audit    # cargo audit
cargo ci-deny     # cargo deny check
```

### Development

```bash
cargo dev         # Quick check (cargo check --all-features)
cargo q           # Same as dev (quick)
cargo check-all   # Check all targets
```

### Documentation

```bash
cargo doc-open    # Build and open docs in browser
cargo doc-all     # Build docs including private items
```

### Dependencies

```bash
cargo tree        # Show dependency tree
cargo tree-dups   # Show duplicate dependencies
cargo license     # Check license compliance
cargo dead_code   # Find unused code (cargo-machete)
```

### Building for Production

```bash
cargo br          # Build release for Linux musl
cargo stackai     # Run release build with args (e.g., cargo stackai deploy status)
```

***

## Local Development Workflow

### Method 1: `cargo run` (Recommended)

```bash
# Run any command directly
cargo run -- init --license YOUR_LICENSE_KEY
cargo run -- deploy status
cargo run -- config secrets --reveal
cargo run -- system import --dry-run

# With environment variables
STACKAI_LICENSE_KEY=your-key cargo run -- init
```

### Method 2: Build and Run Binary

```bash
# Debug build (fast compilation)
cargo build
./target/debug/stackai deploy status

# Release build (optimized)
cargo build --release
./target/release/stackai deploy status
```

### Method 3: Install Locally

```bash
# Install to ~/.cargo/bin
cargo install --path .
stackai --version
```

### Fast Iteration with Bacon

[Bacon](https://github.com/Canop/bacon) is the modern replacement for cargo-watch:

```bash
# Install bacon
cargo install bacon

# Run bacon (default: clippy)
bacon

# Continuous test runner
bacon test

# Run clippy continuously
bacon clippy

# Run a specific job
bacon run
```

Create a `bacon.toml` for custom jobs:

```toml
# bacon.toml (optional - for custom workflows)
[jobs.run-status]
command = ["cargo", "run", "--", "deploy", "status"]
need_stdout = true
watch = ["src"]

[jobs.run-doctor]
command = ["cargo", "run", "--", "diagnose", "doctor"]
need_stdout = true
watch = ["src"]
```

Then run `bacon run-status` or `bacon run-doctor`.

***

## Testing Strategy

### Unit Tests

```bash
# Run all unit tests
cargo test --all-features --lib

# Run with output visible
cargo test-all

# Run specific test
cargo test vault::tests::test_encrypt_decrypt

# Run tests matching pattern
cargo test vault
```

### Documentation Tests

> ⚠️ **Important**: `cargo-nextest` does NOT run doctests. Run them separately:

```bash
# Run doctests only
cargo test-doc
# or
cargo test --all-features --doc
```

### Using cargo-nextest (3x Faster)

```bash
# Install nextest
cargo install cargo-nextest

# Run tests (faster than cargo test)
cargo nextest run --all-features

# With output
cargo nextest run --all-features --no-capture

# Run only changed tests
cargo nextest run --all-features --changed-since HEAD~1

# Remember: doctests need separate run
cargo test --doc
```

### Integration Testing for CLI Commands

For testing CLI output, consider adding these dev-dependencies:

```toml
# Cargo.toml [dev-dependencies]
assert_cmd = "2.0"     # CLI integration testing
predicates = "3.1"     # Assertions for assert_cmd
trycmd = "0.15"        # Snapshot testing for CLI
insta = "1.40"         # General snapshot testing
```

Example integration test with `assert_cmd`:

```rust
// tests/cli_integration.rs
use assert_cmd::Command;
use predicates::prelude::*;

#[test]
fn test_help_output() {
    Command::cargo_bin("stackai")
        .unwrap()
        .arg("--help")
        .assert()
        .success()
        .stdout(predicate::str::contains("StackAI"));
}

#[test]
fn test_version_output() {
    Command::cargo_bin("stackai")
        .unwrap()
        .arg("--version")
        .assert()
        .success();
}
```

***

## Code Quality Checks

### Full CI Check (What the Pre-commit Hook Runs)

```bash
# Using aliases
cargo format       # Check formatting
cargo lint         # Clippy
cargo ci-test      # Tests
cargo ci-audit     # Security audit
cargo ci-deny      # License/dependency checks

# Or run the pre-commit script directly
./.cargo-husky/hooks/pre-commit
```

### Individual Checks

```bash
# Format
cargo fmt --check --all    # Check
cargo fmt --all            # Fix

# Lint
cargo clippy --all-features -- -D warnings

# Security
cargo audit

# License compliance
cargo deny check

# Dead code
cargo machete
```

***

## Safe Testing (Isolated Environment)

> ⚠️ **Never modify your real `~/.config/stackai/` during development!**

### Use XDG\_CONFIG\_HOME for Isolation

The CLI uses `directories-next` which respects `XDG_CONFIG_HOME`:

```bash
# Create isolated test environment
export TEST_DIR=$(mktemp -d)
export XDG_CONFIG_HOME="$TEST_DIR"

# Now ~/.config/stackai/ resolves to $TEST_DIR/stackai/
cargo run -- init --license TEST_LICENSE

# Verify isolation
ls "$TEST_DIR/stackai/"

# Cleanup when done
rm -rf "$TEST_DIR"
```

### Test Script

Create a helper script for isolated testing:

```bash
#!/bin/bash
# scripts/test-isolated.sh

set -e
TEST_DIR=$(mktemp -d)
export XDG_CONFIG_HOME="$TEST_DIR"

echo "🧪 Testing in isolated environment: $TEST_DIR"

# Run your test commands
cargo run -- "$@"

# Cleanup
rm -rf "$TEST_DIR"
echo "✅ Cleanup complete"
```

Usage:

```bash
chmod +x scripts/test-isolated.sh
./scripts/test-isolated.sh init --license TEST_KEY
./scripts/test-isolated.sh deploy status
```

### Testing Docker-Dependent Commands

Some commands require Docker. Mark tests appropriately:

```rust
#[test]
#[ignore = "requires Docker"]
fn test_docker_command() {
    // Test that needs Docker
}
```

Run Docker-dependent tests explicitly:

```bash
# Skip Docker tests (default)
cargo test

# Include Docker tests
cargo test -- --include-ignored
```

***

## Adding a New Command

### 1. Define in `cli.rs`

```rust
#[derive(Subcommand)]
pub enum SystemCommands {
    // ... existing commands ...

    /// Your new command description
    NewCommand {
        /// Flag description
        #[arg(long)]
        some_flag: bool,
    },
}
```

### 2. Create Implementation

```rust
// src/commands/system/newcommand.rs
use crate::commands::{print_success, print_error};
use anyhow::Result;

pub async fn run(some_flag: bool) -> Result<()> {
    // Implementation
    print_success("Command completed!");
    Ok(())
}
```

### 3. Export in mod.rs

```rust
// src/commands/system/mod.rs
pub mod newcommand;
```

### 4. Wire Up Dispatch

In `main.rs` or the parent command handler, add the match arm.

### 5. Add Tests

```rust
// In src/commands/system/newcommand.rs
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_new_command_basic() {
        let result = run(false).await;
        assert!(result.is_ok());
    }
}
```

### 6. Test Locally

```bash
cargo run -- system new-command --some-flag
```

***

## Pull Request Workflow

### Branch Naming

Use descriptive branch names:

```bash
git checkout -b feat/add-new-command
git checkout -b fix/docker-connection-error
git checkout -b docs/update-readme
```

### Commit Messages

Use [Conventional Commits](https://www.conventionalcommits.org/):

```
feat: add system import command for legacy migrations
fix: handle Docker connection timeout gracefully
docs: update README with import examples
refactor: extract Docker client into separate module
test: add integration tests for vault encryption
chore: update dependencies
```

### Before Pushing

```bash
# 1. Format code
cargo fformat

# 2. Run all checks
cargo lint
cargo test-all
cargo ci-audit
cargo ci-deny

# 3. Or run pre-commit hook manually
./.cargo-husky/hooks/pre-commit
```

### PR Checklist

* [ ] Code compiles without warnings (`cargo lint`)
* [ ] All tests pass (`cargo test-all`)
* [ ] New functionality has tests
* [ ] Documentation updated if needed
* [ ] Commit messages follow conventional format
* [ ] No secrets or credentials in code

***

## Debugging Tips

### Enable Debug Logging

```bash
# Set log level
RUST_LOG=debug cargo run -- deploy status
RUST_LOG=trace cargo run -- init
RUST_LOG=stackai=debug cargo run -- deploy status
```

### Debug Output in Code

```rust
tracing::debug!("Variable value: {:?}", my_var);
tracing::info!("Processing step X");
tracing::warn!("Unexpected state: {}", state);
tracing::error!("Failed to connect: {}", err);
```

### VS Code Debugging

Add to `.vscode/launch.json`:

```json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "lldb",
      "request": "launch",
      "name": "Debug stackai",
      "cargo": {
        "args": ["build", "--bin=stackai", "--package=stackai"],
        "filter": { "name": "stackai", "kind": "bin" }
      },
      "args": ["deploy", "status"],
      "cwd": "${workspaceFolder}",
      "env": { "RUST_LOG": "debug" }
    }
  ]
}
```

***

## Build Performance

### Linker (Linux)

> ✅ As of **Rust 1.90.0 (September 2025)**, LLD is the **default linker** on `x86_64-unknown-linux-gnu`. No configuration needed!

For even faster linking on Linux, use mold:

```bash
# Install mold
sudo apt install mold

# Use mold
RUSTFLAGS="-C link-arg=-fuse-ld=mold" cargo build
```

### Linker (macOS)

On macOS, consider `sold` (mold's macOS version):

```bash
# Install sold (if available via Homebrew)
brew install sold

# Or stick with default linker (usually sufficient)
```

### Reduce Debug Info

Add to `Cargo.toml` for faster debug builds:

```toml
[profile.dev]
debug = "line-tables-only"  # Faster builds, still get backtraces

[profile.dev.package."*"]
debug = false  # No debug info for dependencies
```

### Incremental Compilation

Debug builds use incremental compilation by default. For fastest iteration:

```bash
# Just use cargo build/run - it's already incremental
cargo build

# Or use bacon for continuous builds
bacon
```

***

## Common Issues

### "Docker daemon not running"

```bash
# Linux
sudo systemctl start docker

# macOS
open -a Docker
```

### "Permission denied" for Docker

```bash
# Add user to docker group
sudo usermod -aG docker $USER
newgrp docker
```

### Clippy Warnings

```bash
# See all warnings
cargo clippy --all-features 2>&1 | less

# Auto-fix some issues
cargo clippy --fix --all-features --allow-dirty
```

### Test Failures

```bash
# Run specific test with output
cargo test test_name -- --nocapture

# With backtrace
RUST_BACKTRACE=1 cargo test test_name

# Single-threaded (for debugging race conditions)
cargo test -- --test-threads=1
```

### Pre-commit Hook Failures

```bash
# Run each check individually to find the issue
cargo format
cargo lint
cargo test-all
cargo ci-audit
cargo ci-deny

# Bypass hook for WIP commits (use sparingly!)
git commit --no-verify -m "WIP: work in progress"
```

### Build Cache Issues

```bash
# Clean and rebuild
cargo clean
cargo build
```

***

## Project Structure

```
stackai-auto-cli/
├── .cargo/
│   └── config.toml      # Cargo aliases (USE THESE!)
├── .cargo-husky/
│   └── hooks/
│       └── pre-commit   # Pre-commit checks
├── src/
│   ├── main.rs          # Entry point
│   ├── cli.rs           # Clap command definitions
│   ├── lib.rs           # Library exports
│   ├── commands/        # Command implementations
│   │   ├── mod.rs       # Shared utilities
│   │   ├── init.rs      # stackai init
│   │   ├── deploy/      # deploy {start,stop,status,logs,restart}
│   │   ├── config/      # config {domains,tls,secrets,...}
│   │   ├── system/      # system {backup,restore,update,...}
│   │   └── diagnose/    # diagnose {doctor,support,info}
│   ├── docker.rs        # Bollard Docker API
│   ├── compose.rs       # Docker Compose parsing
│   ├── config.rs        # YAML config management
│   ├── vault.rs         # Encrypted secrets (AES-256-GCM)
│   ├── validation.rs    # Input validation
│   ├── templates.rs     # Minijinja templates
│   └── errors.rs        # Error types
├── config/              # Template files
├── Cargo.toml           # Dependencies
├── clippy.toml          # Clippy configuration
├── deny.toml            # License/dependency checks
├── AGENTS.md            # Architecture decisions
└── CONTRIBUTING.md      # This file
```

***

## Getting Help

* 📖 Read [AGENTS.md](/stackai-auto-cli/agents.md) for architecture decisions
* 🔍 Check existing GitHub issues
* 💬 Ask on Slack
* 📧 Contact the team

***

*Happy hacking! 🦀*


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.stackai.com/stackai-auto-cli/contributing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
