plcbundle 0.9.0-alpha.2

DID PLC Bundle Management Tool
Documentation
# plcbundle-rs Coding Rules

## Core Design Principles

1. **Single Entry Point**: All operations go through `BundleManager`
2. **Options Pattern**: Complex operations use dedicated option structs  
3. **Result Types**: Operations return structured result types, not raw tuples
4. **Streaming by Default**: Use iterators for large datasets
5. **NO DIRECT FILE ACCESS**: CLI commands and server code NEVER open bundle files directly

## Critical Rules

### File Access
- ❌ **NEVER** use `std::fs::File::open` for bundle files in CLI commands or server code
- ❌ **NEVER** use `std::fs::read` for bundle data in CLI commands or server code
- ❌ **NEVER** use `std::fs::remove_file` for bundles in CLI commands or server code
- ❌ **NEVER** access core components (Index, bundle_format, etc.) directly from CLI or server
- ✅ **ALWAYS** use `BundleManager` methods for bundle operations
- ✅ **ALWAYS** add new operations to `BundleManager` API if needed

### API-First Development
- All CLI commands in `src/bin/commands/` must use **ONLY** public `BundleManager` APIs
- All server code must use **ONLY** public `BundleManager` APIs
- If CLI or server code needs file access, add the method to `BundleManager` first
- The `BundleManager` is the single source of truth for all bundle operations

### Examples

**❌ WRONG - Direct file access:**
```rust
let bundle_path = dir.join(format!("{:06}.jsonl.zst", bundle_num));
std::fs::remove_file(&bundle_path)?;
```

**✅ CORRECT - Via BundleManager API:**
```rust
manager.delete_bundle_files(&[bundle_num])?;
```

## Architecture

```
CLI Commands / Server → BundleManager API → Internal Implementation
     ↓                        ↓                       ↓
   Uses                   Provides               Opens files
   Only                   Public API             Directly
```

## When Adding New Features

1. Design the API method in `BundleManager` first
2. Document it in `docs/API.md`
3. Implement the method in `src/manager.rs`
4. Export types in `src/lib.rs` if needed
5. Use the API from CLI command or server

## Reference Documentation

- Full API design: `docs/API.md`
- Bundle format: `docs/BUNDLE_FORMAT.md`
- All public APIs are listed in `docs/API.md` Quick Reference

## Common Patterns

### Loading Bundles
```rust
// ❌ Don't open files directly
let file = File::open(bundle_path)?;

// ✅ Use the API
let result = manager.load_bundle(bundle_num, LoadOptions::default())?;
```

### Getting Operations
```rust
// ❌ Don't parse files directly
let json = std::fs::read_to_string(bundle_path)?;

// ✅ Use the API
let json = manager.get_operation_raw(bundle_num, position)?;
```

### Deleting Bundles
```rust
// ❌ Don't remove files directly
std::fs::remove_file(bundle_path)?;

// ✅ Use the API
manager.delete_bundle_files(&[bundle_num])?;
```

### JSON Parsing
- ✅ **ALWAYS** use `sonic_rs` for JSON parsing (performance-critical)
- ❌ **NEVER** use `serde_json` for parsing JSON from bundles or operations
- Use `sonic_rs::from_str` for parsing JSON strings
- Use `sonic_rs::Value` and `JsonValueTrait` for accessing JSON values

**Examples:**
```rust
// ✅ CORRECT - Use sonic_rs
use sonic_rs::JsonValueTrait;
if let Ok(value) = sonic_rs::from_str::<sonic_rs::Value>(&line) {
    if let Some(did) = value.get("did").and_then(|v| v.as_str()) {
        // ...
    }
}

// ❌ WRONG - Don't use serde_json
use serde_json::Value;
let value: Value = serde_json::from_str(&line)?;
```

## Testing

**IMPORTANT**: Always test compilation with all features enabled.

When implementing features:
- **Always run**: `cargo check --all-features` or `cargo build --all-features` before considering work complete
- This ensures code compiles with both `cli` and `server` features enabled
- It's very common that code compiles with default features but fails with all features
- Fix any compilation errors that appear with all features enabled

If writing actual tests:
- Test through the public API
- Don't test internal implementation details
- CLI tests should use actual `BundleManager` instances

## CLI Output Formatting

### Bundle Numbers in Human-Readable Output
- ✅ **ALWAYS** display bundle numbers **without leading zeros** in CLI output for humans
- Use `{}` format, NOT `{:06}` format
- Example: `"Bundle: 123"` not `"Bundle: 000123"`
- This applies to all `println!`, `eprintln!`, and `log::*!` statements that display bundle numbers to users
- Exception: File paths and internal identifiers may use zero-padded format if needed

**Examples:**
```rust
// ✅ CORRECT - Human-readable output
println!("  Last bundle: {}", bundle_num);
log::info!("Bundle {} processed", bundle_num);

// ❌ WRONG - Don't use leading zeros for humans
println!("  Last bundle: {:06}", bundle_num);
log::info!("Bundle {:06} processed", bundle_num);
```

## Command File Structure

All command files (`src/cli/cmd_*.rs`) must follow a consistent structure:

1. **Imports** - All `use` statements at the top
2. **Command Definitions** - All `#[derive(Args)]`, `#[derive(Subcommand)]` structs and enums
3. **Trait Implementations** - Any `impl` blocks for command structs (e.g., `impl HasGlobalFlags`)
4. **Run Function** - The main `pub fn run()` function
5. **Helper Functions** - All private helper functions at the end

**Exception**: Helper functions that are referenced in struct definitions (e.g., `value_parser = parse_duration`) must be defined before the struct that uses them.

**Examples:**

**✅ CORRECT - Proper structure:**
```rust
use anyhow::Result;
use clap::Args;
use plcbundle::BundleManager;
use std::path::PathBuf;

#[derive(Args)]
#[command(about = "Example command")]
pub struct ExampleCommand {
    // ...
}

impl HasGlobalFlags for ExampleCommand {
    // ...
}

pub fn run(cmd: ExampleCommand, dir: PathBuf) -> Result<()> {
    // ...
}

fn helper_function() {
    // ...
}
```

**❌ WRONG - Helper function before command struct:**
```rust
use anyhow::Result;
use clap::Args;

fn helper_function() {
    // ...
}

#[derive(Args)]
pub struct ExampleCommand {
    // ...
}
```

## Questions?

If you're implementing a feature and need file access:
1. Check if `BundleManager` already has the method
2. If not, add it to `BundleManager` first
3. Update `docs/API.md` with the new method
4. Then use it from the CLI or server

**Remember: The CLI and server are just thin wrappers around `BundleManager`!**