スキル一覧に戻る

agentic-jumpstart-architecture

bearbinary / Jarvy

0🍴 0📅 2026年1月17日

Architecture guidelines for Jarvy CLI - codebase structure, tool implementation patterns, registry system, platform-specific code organization, and module conventions.

SKILL.md

---
name: "agentic-jumpstart-architecture"
description: "Architecture guidelines for Jarvy CLI - codebase structure, tool implementation patterns, registry system, platform-specific code organization, and module conventions."
---

# Jarvy Architecture Guidelines

This skill provides comprehensive architecture guidance for the Jarvy CLI project.

## Project Overview

Jarvy is a cross-platform CLI tool that provisions development environments from a `jarvy.toml` config file using native package managers (Homebrew, apt/dnf, winget).

## Directory Structure

```
jarvy/
├── src/
│   ├── main.rs              # CLI entry point (clap derive macros)
│   ├── lib.rs               # Public re-exports for integration tests
│   ├── config.rs            # jarvy.toml parsing with serde
│   ├── error_codes.rs       # Standardized exit codes
│   ├── analytics.rs         # OpenTelemetry/tracing setup
│   ├── posthog.rs           # PostHog analytics client
│   ├── setup.rs             # Setup command implementation
│   ├── bootstrap.rs         # Bootstrap command implementation
│   ├── init.rs              # Global initialization logic
│   ├── report.rs            # Tool status reporting
│   ├── provisioner.rs       # Core provisioning orchestration
│   ├── tools/
│   │   ├── mod.rs           # Tool module registry and re-exports
│   │   ├── registry.rs      # Global OnceLock<RwLock<HashMap>> registry
│   │   ├── common.rs        # Shared utilities
│   │   ├── _template.rs     # Scaffold template for new tools
│   │   └── {tool}/          # Tool implementations
│   │       ├── mod.rs       # Re-exports the handler
│   │       └── {tool}.rs    # Implementation
│   └── tests/               # Unit test modules
├── tests/                   # Integration tests
├── crates/
│   └── cargo-jarvy/         # Cargo subcommand for scaffolding
└── Cargo.toml
```

## Core Patterns

### 1. Tool Implementation Pattern

Every tool follows a three-layer architecture:

```rust
// src/tools/{name}/{name}.rs

use crate::tools::common::{InstallError, cmd_satisfies, has, run};
#[cfg(target_os = "linux")]
use crate::tools::common::{detect_linux_pm, PkgOps, default_use_sudo};

/// Registry adapter: allows tools::add("{name}", version) to dispatch here
pub fn add_handler(min_hint: &str) -> Result<(), InstallError> {
    ensure(min_hint)
}

/// Main entry: probe first; install if needed
pub fn ensure(min_hint: &str) -> Result<(), InstallError> {
    if cmd_satisfies("{binary}", min_hint) {
        return Ok(());
    }
    install()
}

fn install() -> Result<(), InstallError> {
    #[cfg(target_os = "macos")]
    { return install_macos(); }
    #[cfg(target_os = "linux")]
    { return install_linux(); }
    #[cfg(target_os = "windows")]
    { return install_windows(); }
    #[allow(unreachable_code)]
    Err(InstallError::Unsupported)
}

#[cfg(target_os = "macos")]
fn install_macos() -> Result<(), InstallError> {
    if !has("brew") {
        return Err(InstallError::Prereq("Homebrew not found. Install https://brew.sh"));
    }
    run("brew", &["install", "{formula}"])?;
    Ok(())
}

#[cfg(target_os = "linux")]
fn install_linux() -> Result<(), InstallError> {
    let pm = detect_linux_pm().ok_or(InstallError::Prereq(
        "No supported package manager (apt/dnf/yum/zypper/pacman/apk)"
    ))?;
    let _ = PkgOps::update(pm, default_use_sudo());
    PkgOps::install(pm, "{package}", default_use_sudo())
}

#[cfg(target_os = "windows")]
fn install_windows() -> Result<(), InstallError> {
    if !has("winget") {
        return Err(InstallError::Prereq("winget not found"));
    }
    run("winget", &["install", "-e", "--id", "{WingetId}"])?;
    Ok(())
}
```

### 2. Registry Pattern

Tools are registered in a global thread-safe registry:

```rust
// src/tools/registry.rs
use std::sync::{OnceLock, RwLock};
use std::collections::HashMap;

pub type ToolAdder = fn(version: &str) -> Result<(), InstallError>;

static REGISTRY: OnceLock<RwLock<HashMap<String, ToolAdder>>> = OnceLock::new();

fn registry() -> &'static RwLock<HashMap<String, ToolAdder>> {
    REGISTRY.get_or_init(|| RwLock::new(HashMap::new()))
}

pub fn register_tool(name: &str, handler: ToolAdder) -> bool {
    let key = name.to_ascii_lowercase();
    let mut map = registry().write().expect("registry rwlock poisoned");
    map.insert(key, handler).is_none()
}

pub fn get_tool(name: &str) -> Option<ToolAdder> {
    let key = name.to_ascii_lowercase();
    let map = registry().read().expect("registry rwlock poisoned");
    map.get(&key).copied()
}

pub fn add(name: &str, version: &str) -> Result<(), InstallError> {
    let key = name.to_ascii_lowercase();
    let map = registry().read().expect("registry rwlock poisoned");
    if let Some(handler) = map.get(&key) {
        let f = *handler;
        drop(map);
        f(version)
    } else {
        Err(InstallError::Parse("unknown tool"))
    }
}
```

### 3. Error Handling Pattern

Use `thiserror` for structured errors:

```rust
// src/tools/common.rs
#[derive(thiserror::Error, Debug)]
pub enum InstallError {
    #[error("unsupported platform")]
    Unsupported,
    #[error("prerequisite missing: {0}")]
    Prereq(&'static str),
    #[error("invalid permissions: {0}")]
    InvalidPermissions(&'static str),
    #[error("command failed: {cmd} (code: {code:?})\n{stderr}")]
    CommandFailed { cmd: String, code: Option<i32>, stderr: String },
    #[error("io error: {0}")]
    Io(#[from] std::io::Error),
    #[error("parse error: {0}")]
    Parse(&'static str),
}
```

### 4. Platform Detection Pattern

```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Os {
    Linux,
    Macos,
    Windows,
}

pub fn current_os() -> Os {
    #[cfg(target_os = "linux")]   { Os::Linux }
    #[cfg(target_os = "macos")]   { Os::Macos }
    #[cfg(target_os = "windows")] { Os::Windows }
}

#[cfg(target_os = "linux")]
pub fn detect_linux_pm() -> Option<PackageManager> {
    // Returns Apt, Dnf, Yum, Zypper, Pacman, or Apk
}
```

### 5. Configuration Pattern

Support both simple and detailed formats in `jarvy.toml`:

```toml
[privileges]
use_sudo = true

[privileges.per_os]
linux = true
macos = false

[provisioner]
# Simple format
git = "2.40"

# Detailed format
docker = { version = "24.0", version_manager = true, use_sudo = false }
```

Parsed with serde untagged enums:

```rust
#[derive(Deserialize)]
#[serde(untagged)]
pub enum ToolConfig {
    Detailed { version: String, version_manager: Option<bool>, use_sudo: Option<bool> },
    Simple(String),
}
```

## Adding a New Tool

### Step 1: Create the tool directory

```bash
cargo run -p cargo-jarvy -- new-tool {toolname}
# Or manually:
mkdir -p src/tools/{toolname}
```

### Step 2: Create mod.rs

```rust
// src/tools/{toolname}/mod.rs
#![allow(clippy::module_inception)]
pub mod {toolname};
```

### Step 3: Create the implementation

Use the template in `src/tools/_template.rs` as a starting point.

### Step 4: Register in src/tools/mod.rs

```rust
// Add module declaration
pub mod {toolname};

// In register_all():
let _ = register_tool("{toolname}", crate::tools::{toolname}::{toolname}::add_handler);
```

## Common Utilities Reference

### Command Execution

```rust
// Run a command and capture output
run(cmd: &str, args: &[&str]) -> Result<Output, InstallError>

// Run with optional sudo (non-Windows)
run_maybe_sudo(use_sudo: bool, cmd: &str, args: &[&str]) -> Result<Output, InstallError>

// Check if command exists on PATH
has(cmd: &str) -> bool

// Check if command output contains version prefix
cmd_satisfies(cmd: &str, min_prefix: &str) -> bool

// Require command or return error
require(cmd: &str, remediation: &'static str) -> Result<(), InstallError>

// Require one of multiple commands
require_any<'a>(candidates: &[&'a str], remediation: &'static str) -> Result<&'a str, InstallError>
```

### Package Manager Operations

```rust
// Update package lists
PkgOps::update(pm: PackageManager, use_sudo: Option<bool>) -> Result<(), InstallError>

// Install a package
PkgOps::install(pm: PackageManager, pkg: &str, use_sudo: Option<bool>) -> Result<(), InstallError>
```

## Exit Codes

| Code | Constant | Meaning |
|------|----------|---------|
| 0 | SUCCESS | Command completed successfully |
| 2 | CONFIG_ERROR | jarvy.toml missing or malformed |
| 3 | PREREQ_MISSING | Required package manager not found |
| 5 | PERMISSION_REQUIRED | Elevated privileges needed |

## CLI Structure

Commands defined with clap derive:

```rust
#[derive(Parser)]
#[clap(name = "jarvy", version, author, about)]
struct Cli {
    #[clap(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    Setup { #[clap(short, long)] file: String },
    Bootstrap {},
    Configure {},
    Get { file: String, output_format: OutputFormat, output: Option<String> },
    #[clap(external_subcommand)]
    External(Vec<String>),
}
```

## Telemetry Architecture

- **PostHog**: Product analytics for usage events and errors
- **OpenTelemetry**: Structured logging via OTLP (HTTP/protobuf)
- **Configuration**: `~/.jarvy/config.toml` with `telemetry = true/false`
- **Environment**: `JARVY_OTLP_ENDPOINT` for custom collectors

## Key Dependencies

| Crate | Purpose |
|-------|---------|
| clap | CLI parsing with derive macros |
| serde/toml | Configuration parsing |
| thiserror | Structured error types |
| tracing | Logging and instrumentation |
| opentelemetry-otlp | Telemetry export |
| inquire | Interactive prompts |
| assert_cmd | Integration testing |

## Conventions

1. **Rust Edition**: 2024
2. **Commit Messages**: Conventional Commits (`feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`)
3. **Formatting**: Always run `cargo fmt --all`
4. **Linting**: `cargo clippy --all-features -- -D warnings` must pass
5. **Dependencies**: Prefer stdlib and existing deps over new crates
6. **Platform Code**: Use `#[cfg(target_os = "...")]` attributes