Skip to content

Latest commit

 

History

History
357 lines (267 loc) · 8.24 KB

File metadata and controls

357 lines (267 loc) · 8.24 KB

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

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

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

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

Rebuild on change

cargo install cargo-watch
cargo watch -x build

Tests

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

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:

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

Documentation

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:
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,
        })
    }
}
  1. Register it in src/checks/mod.rs:
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:

#[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:

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:

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

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

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

Integration tests

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

Debugging

Debug prints

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

RUST_BACKTRACE

RUST_BACKTRACE=1 cargo run -- --quick

RUST_LOG (if you add tracing)

RUST_LOG=debug cargo run -- --quick

Performance

Profiling

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

Benchmarking

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

Release

Build the release binary

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

Cross-compilation (optional)

# 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:

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