plcbundle 0.9.0-alpha.2

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

> **READ THIS FIRST**: All code contributors and AI assistants must follow these rules.

## 🚨 Critical Rules

### 1. NO DIRECT FILE ACCESS FROM CLI OR SERVER

**CLI commands NEVER open bundle files directly**

**CLI commands NEVER access core components (Index, bundle_format, etc.) directly**

**Server code NEVER opens bundle files directly**

**Server code NEVER accesses core components (Index, bundle_format, etc.) directly**

All CLI and server operations MUST go through `BundleManager` public API methods.

### 2. FOLLOW THE SPECIFICATION

**All bundle creation MUST comply with [`docs/specification.md`](docs/specification.md)**

Critical requirements from spec:
- **Preserve raw JSON**: Store exact byte strings from PLC directory, never re-serialize
- **SHA-256 hashing**: Use SHA-256 (not Blake3) for all content/compressed/chain hashes
- **Chain hash formula**: 
  - Genesis: `SHA256("plcbundle:genesis:" + content_hash)`
  - Subsequent: `SHA256(parent_chain_hash + ":" + content_hash)`
- **Newline termination**: Every operation ends with `\n` including the last one

**Before implementing bundle-related features, consult `docs/specification.md` first!**

---

### Rule 1 Details: NO DIRECT FILE ACCESS OR CORE COMPONENT ACCESS

```rust
// ❌ WRONG - Direct file access (from CLI or server)
let file = File::open(bundle_path)?;
let data = std::fs::read(path)?;
std::fs::remove_file(path)?;

// ❌ WRONG - Direct core component access from CLI or server
use plcbundle::Index;
let index = Index::load(&dir)?;
Index::init(&dir, origin, force)?;
Index::rebuild_from_bundles(&dir, origin, callback)?;

// ❌ WRONG - Direct BundleManager::new (no helpful error messages)
let manager = BundleManager::new(dir)?;

// ✅ CORRECT - Via cli::utils::create_manager (with helpful errors)
let manager = super::utils::create_manager(dir, verbose)?;
manager.load_bundle(num, options)?;
manager.get_operation_raw(bundle, pos)?;
manager.delete_bundle_files(&[num])?;

// For static operations (init/rebuild don't need existing repository)
BundleManager::init_repository(origin, force)?;
BundleManager::rebuild_index(origin, callback)?;
```

## Architecture

All operations flow through `BundleManager`:

```
┌─────────────┐      ┌──────────────────┐      ┌────────────────┐      ┌──────────────┐
│ CLI Command │─────→│  BundleManager   │─────→│ Core Modules   │─────→│ File System  │
│             │      │   (Public API)   │      │ (Index, etc.)  │      │              │
└─────────────┘      └──────────────────┘      └────────────────┘      └──────────────┘
     Uses                 Provides                Internal Use             Direct Access
     Only                Public API               Only                     Only Here
```

**Key principle:** CLI commands and server code should ONLY interact with `BundleManager`, never with:
- `Index` directly
- `bundle_format` functions directly
- Direct file I/O (`std::fs`, `File::open`, etc.)
- Any other core module directly

### Why This Rule Exists

1. **Single Source of Truth**: All file operations go through one place
2. **Consistency**: Same behavior across CLI, Go bindings, and library users
3. **Caching**: BundleManager handles caching transparently
4. **Testing**: Easy to mock and test through clean API
5. **Safety**: Centralized error handling and validation

## 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. **Modular Architecture**: `manager.rs` orchestrates, functionality lives in dedicated modules

## Module Organization

Functionality should be split into logical modules under `/src`:

```
src/
├── manager.rs          # Orchestrates components, provides public API
├── bundle_loading.rs   # Bundle loading operations
├── bundle_format.rs    # Bundle format (frames, compression)
├── operations.rs       # Operation types and filters
├── query.rs           # Query engine
├── export.rs          # Export operations
├── verification.rs    # Bundle verification
├── did_index.rs       # DID indexing
├── resolver.rs        # DID resolution
├── mempool.rs         # Mempool operations
├── sync.rs            # Sync from PLC directory
└── cache.rs           # Caching layer
```

**manager.rs should:**
- Define `BundleManager` struct
- Provide clean public API methods
- Delegate to specialized modules
- **NOT** contain complex implementation logic

**Specialized modules should:**
- Contain the actual implementation
- Be used by `manager.rs`
- Can have internal functions not exposed in public API

## When Adding New Features

### Step-by-Step Process

1. **Design the API signature** in `manager.rs`
2. **Document it** in `docs/API.md`
3. **Implement in appropriate module** (or create new module)
4. **Export types** in `src/lib.rs` if public
5. **Use from CLI or server** through the public API

### Example: Adding a New Feature

```rust
// 1. Add to manager.rs (public API)
impl BundleManager {
    pub fn new_feature(&self, param: Param) -> Result<Output> {
        // Delegate to specialized module
        specialized_module::do_the_work(self, param)
    }
}

// 2. Implement in specialized_module.rs
pub(crate) fn do_the_work(manager: &BundleManager, param: Param) -> Result<Output> {
    // Complex logic here
}

// 3. Use from CLI or server
pub fn cmd_new_feature(dir: PathBuf, param: Param) -> Result<()> {
    let manager = BundleManager::new(dir)?;
    let result = manager.new_feature(param)?;  // ✅ Via API
    // Display result
    Ok(())
}
```

## Path Resolution

### Always Resolve "." to Full Path
- **NEVER** display "." in user-facing output
-**ALWAYS** resolve "." to canonical/absolute path using `std::fs::canonicalize`
- This applies to all CLI commands that display paths to users

**Example:**
```rust
// ❌ WRONG - shows "." to user
eprintln!("Working in: {}", dir.display());

// ✅ CORRECT - resolve "." to actual path
let display_path = if dir.as_os_str() == "." {
    std::fs::canonicalize(".").unwrap_or_else(|_| dir.clone())
} else {
    std::fs::canonicalize(dir).unwrap_or_else(|_| dir.clone())
};
eprintln!("Working in: {}", display_path.display());
```

## Common Mistakes to Avoid

### ❌ Don't Do This

```rust
// CLI command or server code opening files directly
let bundle_path = dir.join(format!("{:06}.jsonl.zst", num));
let file = File::open(bundle_path)?;
let decoder = zstd::Decoder::new(file)?;

// CLI command or server code accessing Index directly
use plcbundle::Index;
let index = Index::rebuild_from_bundles(&dir, origin, callback)?;
index.save(&dir)?;

// CLI command or server code accessing bundle_format directly
use plcbundle::bundle_format;
let ops = bundle_format::load_bundle_as_json_strings(&path)?;
```

### ✅ Do This Instead

```rust
// CLI command or server code using BundleManager API only
let manager = BundleManager::new(dir)?;

// Loading bundles
let result = manager.load_bundle(num, LoadOptions::default())?;

// Rebuilding index
manager.rebuild_index(origin, callback)?;

// Initializing repository
manager.init_repository(origin, force)?;
```

### ❌ Don't Do This

```rust
// Complex logic in manager.rs
impl BundleManager {
    pub fn complex_operation(&self) -> Result<Output> {
        // 200 lines of implementation
        // parsing, processing, formatting...
    }
}
```

### ✅ Do This Instead

```rust
// Manager delegates to specialized module
impl BundleManager {
    pub fn complex_operation(&self) -> Result<Output> {
        specialized_module::perform_complex_operation(self)
    }
}

// Implementation in specialized_module.rs
pub(crate) fn perform_complex_operation(manager: &BundleManager) -> Result<Output> {
    // 200 lines of implementation here
}
```

## Testing Guidelines

- Test through the public API, not internal implementation
- CLI tests should use `BundleManager` instances
- Mock file system through `BundleManager` in tests
- Integration tests in `tests/` directory

## Documentation

When adding/changing APIs:

1. Update `docs/API.md` with method signature and examples
2. Add doc comments to public functions
3. Update CHANGELOG.md if user-facing

## Questions?

**Need file access?**
1. Check if `BundleManager` has the method ✅
2. If not, add it to `BundleManager` first ✅
3. Implement in appropriate module ✅
4. Update `docs/API.md`5. Use from CLI or server ✅

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

---

## CLI Module Naming

### `cmd_` Prefix Only for Commands

The `cmd_` prefix should **only** be used for actual CLI commands (subcommands that users invoke). Helper modules and utilities should **not** have the `cmd_` prefix.

**Structure:**
```
src/cli/
  ├── plcbundle-rs.rs      (main entry point)
  ├── cmd_*.rs             (CLI commands only)
  ├── progress.rs          (helper: progress bar)
  ├── utils.rs             (helper: utility functions)
  └── logger.rs            (helper: logging setup)
```

**Rules:**
- `cmd_export.rs` - CLI command
-`cmd_query.rs` - CLI command
-`cmd_verify.rs` - CLI command
-`cmd_utils.rs` - Should be `utils.rs` (helper)
-`cmd_progress.rs` - Should be `progress.rs` (helper)
-`cmd_logger.rs` - Should be `logger.rs` (helper)

**Rationale:** This makes it immediately clear which files are user-facing commands vs internal helpers when browsing the codebase.

---

See also:
- **`docs/specification.md`** - **Official PLC Bundle V1 specification (MUST READ)**
- `docs/API.md` - Complete API reference
- `docs/BUNDLE_FORMAT.md` - Bundle file format details
- `.cursorrules` - Cursor-specific rules