# How to Use the ReviewStore Rust Library API for Programmatic Review Sessions in tuicr

> Learn to use the ReviewStore Rust library API to programmatically manage code review sessions in tuicr. Create, query, and mutate reviews with atomic operations.

- Repository: [Almog Gavra/tuicr](https://github.com/agavra/tuicr)
- Tags: api-reference
- Published: 2026-08-02

---

**The `ReviewStore` API in `agavra/tuicr` exposes a programmatic interface for creating, querying, and mutating code review sessions without launching the TUI, supporting atomic operations via `add_comment`, `get_review`, and `save_review` methods.**

The `tuicr` crate ships a pure-Rust library in [`src/review_store.rs`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs) that enables developers to automate code review workflows. By leveraging the **ReviewStore Rust library API**, you can integrate persisted review sessions into custom tooling, CI pipelines, or headless automation scripts.

## Core Types and Architecture

The library centers on a few key structs that manage persistence and in-memory representation.

### ReviewStore and SessionRef

The **`ReviewStore`** struct acts as the main facade for managing the directory where sessions are stored. It provides constructors and methods to list, load, and mutate sessions. A **`SessionRef`** serves as an opaque handle to a single session file, implemented as a `PathBuf` wrapper.

According to the source in [`src/review_store.rs#L10-L41`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L10-L41), you initialize the store using either the default platform directory or a custom path.

### Data Models

- **`SessionSummary`**: Lightweight metadata returned by listing methods, containing the slug, kind, timestamps, comment count, and file count ([`src/review_store.rs#L59-L71`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L59-L71)).
- **`ReviewSession`**: The full in-memory representation of a session, including files, comments, and reviewed hunk state ([`src/model/review.rs#L89-L124`](https://github.com/agavra/tuicr/blob/main/src/model/review.rs#L89-L124)).
- **`Comment`**: Individual comment objects with content, type, author, lifecycle state, and optional commit scoping ([`src/model/comment.rs#L55-L78`](https://github.com/agavra/tuicr/blob/main/src/model/comment.rs#L55-L78)).

### Request Types

The **`AddCommentRequest`** struct describes a new draft comment, specifying the target, content, type, author, and optional commit SHA ([`src/review_store.rs#L73-L88`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L73-L88)). The **`CommentTarget`** enum determines attachment points: review-level, file-level, single line, or line-range ([`src/review_store.rs#L90-L102`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L90-L102)).

## Initializing the Store

You have two entry points for creating a `ReviewStore` instance.

**`ReviewStore::new()`** builds a store using the platform-default reviews directory, typically `~/.local/share/tuicr/reviews` on Linux ([`src/review_store.rs#L16-L21`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L16-L21)).

**`ReviewStore::with_reviews_dir(dir)`** allows you to override the storage location, which is essential for tests or isolated tooling environments ([`src/review_store.rs#L22-L28`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L22-L28)).

```rust
use tuicr::review_store::ReviewStore;
use std::path::Path;

// Use the default platform directory
let store = ReviewStore::new();

// Or use a temporary isolated directory
let temp_dir = std::env::temp_dir().join("my_tuicr_reviews");
let store = ReviewStore::with_reviews_dir(temp_dir);

```

## Querying Sessions

### Listing Sessions

Use **`list_sessions_for_repo(selector)`** to retrieve all persisted sessions belonging to a specific checkout path or forge coordinate (e.g., `owner/repo`) ([`src/review_store.rs#L34-L45`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L34-L45)).

Use **`list_all_sessions()`** to retrieve every session ordered newest-first, including both local and PR sessions ([`src/review_store.rs#L47-L55`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L47-L55)).

```rust
use std::path::Path;

// List sessions for a specific repository checkout
let repo_path = Path::new("/home/me/project");
let sessions = store
    .list_sessions_for_repo(repo_path)
    .expect("failed to list sessions");

// Print session metadata
for s in sessions {
    println!(
        "{} | {} | {} comments | {} files",
        s.slug, s.kind.id(), s.comment_count, s.file_count
    );
}

```

### Resolving PR Sessions

The **`resolve_pr_session(slug)`** method converts a PR slug (e.g., `gh:owner/repo/pr/42`) into a `SessionRef` if the session exists on disk ([`src/review_store.rs#L59-L66`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L59-L66)).

```rust
let slug = "gh:agavra/tuicr/pr/42";
if let Some(session_ref) = store
    .resolve_pr_session(slug)
    .expect("failed to resolve PR")
{
    let pr_review = store.get_review(&session_ref).expect("could not load PR session");
    // Work with pr_review...
}

```

## Loading and Inspecting Sessions

Call **`get_review(&session_ref)`** to load the full `ReviewSession` from disk ([`src/review_store.rs#L67-L71`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L67-L71)).

```rust
// Assuming 'first' is a SessionSummary from list_sessions_for_repo
let session_ref = &first.session_ref;
let mut review = store
    .get_review(session_ref)
    .expect("could not load session");

// Inspect review-level comments
for c in &review.review_comments {
    println!("{}: {}", c.author, c.content);
}

// Inspect file-level and line-level comments
for (path, file_rev) in &review.files {
    for c in &file_rev.file_comments {
        println!("File {}: {}", path.display(), c.content);
    }
    for (line, comments) in &file_rev.line_comments {
        for c in comments {
            println!("Line {}:{} - {}", path.display(), line, c.content);
        }
    }
}

```

## Mutating Sessions

### Adding Comments

The **`add_comment(&session_ref, request)`** method appends a local draft comment to a session and persists it atomically. Internally, it delegates to `add_comment_to_session`, which validates content, creates a `Comment` object, inserts it based on the `CommentTarget`, and updates the session's `updated_at` timestamp ([`src/review_store.rs#L72-L84`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L72-L84)).

```rust
use tuicr::review_store::{AddCommentRequest, CommentTarget};
use tuicr::model::{CommentType, LineSide};

let req = AddCommentRequest {
    target: CommentTarget::Line {
        path: std::path::PathBuf::from("src/main.rs"),
        line: 42,
        side: LineSide::New,
    },
    content: "Consider extracting this block into a helper function".into(),
    comment_type: CommentType::from_id("suggestion"),
    author: "my-bot".into(),
    commit_id: None,
};

store
    .add_comment(&session_ref, req)
    .expect("failed to add comment");

```

### Saving Modified Sessions

After in-place modifications to a `ReviewSession`, call **`save_review(&session)`** to persist changes and receive an updated `SessionRef` ([`src/review_store.rs#L86-L90`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L86-L90)).

```rust
// Modify the session in-place
review.session_notes = Some("Reviewed on 2026-08-02".into());

// Persist changes
let new_ref = store
    .save_review(&review)
    .expect("could not save session");

println!("Session saved at {}", new_ref.path().display());

```

## Persistence Mechanics and Thread Safety

All I/O delegates to `crate::persistence::storage`, which writes sessions under `$XDG_DATA_HOME/tuicr/reviews/` (or the custom directory) and maintains manifests for fast listing ([`src/review_store.rs#L92-L98`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L92-L98)).

**`add_comment`** and **`save_review`** use `storage::update_session_in_dir` and `storage::save_session_in_dir`, which lock the session file, merge external changes, and perform atomic rename operations on temporary files. This ensures that concurrent CLI tools and custom scripts do not corrupt session data.

## Summary

- **Initialize** the store with `ReviewStore::new()` or `with_reviews_dir()` to control storage location.
- **Query** existing sessions using `list_sessions_for_repo()`, `list_all_sessions()`, or `resolve_pr_session()`.
- **Load** full session data with `get_review()` to access `ReviewSession` and its `Comment` collections.
- **Mutate** sessions by constructing `AddCommentRequest` with `CommentTarget` variants and calling `add_comment()`.
- **Persist** changes atomically via `save_review()`, which handles file locking and conflict resolution.

## Frequently Asked Questions

### How do I configure a custom storage directory for ReviewStore?

Use the **`ReviewStore::with_reviews_dir(path)`** constructor instead of `new()`. This overrides the default platform directory and is useful for testing or isolated tooling environments. The method is defined in [`src/review_store.rs#L22-L28`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs#L22-L28).

### Is the ReviewStore API thread-safe for concurrent access?

Yes. The `add_comment` and `save_review` methods use file locking via `storage::update_session_in_dir` and atomic rename operations. Concurrent processes (such as multiple CLI invocations or background tasks) can safely read and write sessions without data corruption.

### What is the difference between CommentTarget::Line and CommentTarget::LineRange?

`CommentTarget::Line` attaches a comment to a single specific line with a `LineSide` (Old or New), while `CommentTarget::LineRange` attaches to a span of lines. Both variants are used when constructing `AddCommentRequest` to specify where the comment appears in the diff.

### How do I add a comment to a specific commit rather than the full diff?

Include the optional `commit_id` field in your `AddCommentRequest`. When `commit_id` is `Some(sha)`, the comment targets that specific commit; when `None`, it applies to the full diff session. This aligns with the `Comment` struct's commit scoping as defined in [[`src/model/comment.rs`](https://github.com/agavra/tuicr/blob/main/src/model/comment.rs)](https://github.com/agavra/tuicr/blob/main/src/model/comment.rs).