prview 0.4.0

PR Review & Artifact Generator - cross-language PR analysis tool
Documentation
# Developer guide

This document covers working on the `prview` repo.
If you just want to use the tool, see `docs/usage.md`.
If you want to understand the module layout and execution flow, see `docs/architecture.md`.

## Setup

```bash
git clone https://github.com/vetcoders/prview-rs.git
cd prview-rs
cargo build
```

## Development workflow

### Branching model (trunk-based on `main`)

This repo is trunk-based:

- `main` is the trunk and the stable release branch
- feature / fix / chore branches (`feat/*`, `fix/*`, `chore/*`) are created from `main`
- every branch opens a PR back into `main`
- PRs land as merge commits (no squash)
- release tags (`v*`) are cut from `main`

### Build & run

```bash
# Debug build (faster compile)
cargo build
./target/debug/prview --help

# Release build (optimized)
cargo build --release
./target/release/prview --quick
```

### Rebuild on change

```bash
cargo install cargo-watch
cargo watch -x build
```

### Tests

```bash
cargo test --lib
cargo test --test json_contract
```

For Rust review flow, a standard `prview` run executes tests by default.
You disable them only with a lighter preset (`--quick`, `--update`, `--ai-only`)
or an explicit `--skip-tests`. Exception: standard `--remote-only` is now a
deliberately faster preset, so tests must be restored there with `--with-tests`
or by using `--deep`.

### Linting

```bash
cargo clippy -- -D warnings
cargo fmt --all
```

Likewise, lint is on by default for Rust in standard mode. `--with-lint` mostly
matters when you want to restore it after a lighter preset. Under standard
`--remote-only`, heavy Rust lint (`clippy`, `rustfmt`) is trimmed, so `--with-lint`
or `--deep` restores the fuller pass.

### Local gate

Before pushing, run the same gate CI runs:

```bash
make precommit   # fast pre-commit gate
make check       # full local gate (fmt + clippy + tests)
```

### Documentation

```bash
cargo doc --open
```

## Code structure

```
src/
├── main.rs            # Entry point
├── lib.rs             # App orchestration
├── cli/mod.rs         # CLI parsing (clap)
├── config/mod.rs      # Configuration & profile detection
├── git/mod.rs         # Git operations (git2, Patch API)
├── checks/            # Quality checks (trait-based)
├── heuristics/        # Structural analysis (loctree)
├── artifacts/
│   ├── mod.rs         # Core layout, patches, merge gate, ZIP
│   ├── signal/        # High-signal generators (one module per domain)
│   └── dashboard.rs   # HTML dashboard
├── mcp/               # MCP server (stdio) for agent integrations
├── scope/             # Scoped review packs (filter by files/commits)
├── policy/            # Policy parser + blocking semantics
├── regression/        # Regression detection (diff, perf, deps, score)
├── state/             # Incremental repo state
├── storage/           # Persistent run storage ($PRVIEW_HOME)
├── cache/             # Hash-based caching
├── tui/               # Terminal UI mode
├── output/            # Terminal output formatting
├── proc.rs            # Subprocess hardening (process groups, timeouts)
├── check_id.rs        # Stable check identifiers
└── paths.rs           # Repo-bounded path validation
```

## Adding a new check

1. **Create the file** `src/checks/newcheck.rs`:

```rust
use super::{run_command, Check, CheckResult, CheckStatus};
use crate::Config;
use anyhow::Result;
use async_trait::async_trait;

pub struct NewCheck;

#[async_trait]
impl Check for NewCheck {
    fn name(&self) -> &str {
        "NewCheck"
    }

    fn can_run(&self, config: &Config) -> bool {
        // When this check makes sense
        config.profile.has_something
    }

    async fn run(&self, config: &Config) -> Result<CheckResult> {
        let start = std::time::Instant::now();

        let output = run_command("mytool", &["--check"], &config.repo_root).await?;

        let status = if output.status.success() {
            CheckStatus::Passed
        } else {
            CheckStatus::Failed
        };

        Ok(CheckResult {
            name: self.name().to_string(),
            status,
            duration: start.elapsed(),
            output: String::from_utf8_lossy(&output.stdout).to_string(),
            cached: false,
        })
    }
}
```

2. **Register it in** `src/checks/mod.rs`:

```rust
mod newcheck;
pub use newcheck::NewCheck;

fn get_checks_for_profile(config: &Config) -> Vec<Box<dyn Check>> {
    let mut checks = Vec::new();

    // Add in the right place
    if config.run_lint {
        checks.push(Box::new(NewCheck));
    }

    checks
}
```

## Adding a new profile

In `src/config/mod.rs`:

```rust
#[derive(Debug, Clone, Copy)]
pub enum ProfileKind {
    Js,
    Rust,
    Python,
    Mixed,
    Generic,
    NewLanguage,  // Add
}

fn detect_profile(repo_root: &PathBuf, requested: Profile) -> Result<DetectedProfile> {
    // Add detection
    let has_newlang = repo_root.join("newlang.config").exists();

    // ...
}
```

## Adding a new artifact

Two places depending on the type:

### Core artifact (always generated)

In `src/artifacts/mod.rs`, add it to the right numbered-layout section:

```rust
pub fn generate(...) -> Result<PathBuf> {
    // ...
    // 20_quality/
    generate_my_artifact(&quality_dir, diffs)?;
    // ...
}

fn generate_my_artifact(dir: &Path, diffs: &[Diff]) -> Result<()> {
    let content = "...";
    std::fs::write(dir.join("my_artifact.txt"), content)?;
    Ok(())
}
```

### Signal generator (only written when it has meaningful data)

Signal generators live in `src/artifacts/signal/`, one module per domain. Add
`src/artifacts/signal/my_signal.rs`:

```rust
pub fn generate_my_signal(dir: &Path, diffs: &[Diff]) -> Result<()> {
    let findings = analyze(diffs);
    if findings.is_empty() {
        return Ok(()); // No file = no noise
    }
    std::fs::write(dir.join("my_signal.txt"), format_findings(&findings))?;
    Ok(())
}
```

Then register it in `src/artifacts/signal/mod.rs` (`mod my_signal; pub use my_signal::*;`)
and call it from `artifacts/mod.rs` in the signal-generators section, with status logging.

### If you change the artifact-pack contract

When you change the layout, filenames, or review-flow semantics, update these
together with the code:

- `README.md` for the quick entry point
- `docs/usage.md` for the output layout and artifact interpretation
- `docs/architecture.md` for the module map and responsibilities
- `docs/contracts/merge_gate.md` if the `MERGE_GATE.json` schema or paths change

Pay special attention to root-level navigators (`AI_INDEX.md`, `PR_REVIEW.md`,
`report.json`) and the numbered layout in `00_summary/`, `10_diff/`,
`20_quality/`, `30_context/`.

The `--json` stdout is not a full export of the artifact pack. Treat it as a
compact contract for automation; the full truth about a run stays in the
artifacts written to disk.

## Testing

### Unit tests

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_profile_detection() {
        // ...
    }
}
```

### Integration tests

```bash
# Test against a real repo
cd /path/to/some/repo
/path/to/prview/target/debug/prview --quick
```

## Debugging

### Debug prints

```rust
// Temporary debug prints
eprintln!("DEBUG: {:?}", variable);
```

### RUST_BACKTRACE

```bash
RUST_BACKTRACE=1 cargo run -- --quick
```

### RUST_LOG (if you add tracing)

```bash
RUST_LOG=debug cargo run -- --quick
```

## Performance

### Profiling

```bash
cargo build --release
perf record ./target/release/prview --quick
perf report
```

### Benchmarking

```bash
hyperfine './target/release/prview --quick'
```

## Release

### Build the release binary

```bash
cargo build --release
ls -la target/release/prview
```

### Cross-compilation (optional)

```bash
# Linux (from Mac)
cargo build --release --target x86_64-unknown-linux-gnu

# Windows (from Mac)
cargo build --release --target x86_64-pc-windows-gnu
```

## Status note

This document describes the current way of working on the repo and extending the
code. It is not a product roadmap.

For the current state of CLI features:

```bash
prview --help
```

For the latest design decisions:

- check open PRs against `main`
- read `README.md` as the repo-level entry point
- treat historical plans under `docs/plans/` as a record of work, not a source of truth
```