# ADR-014: Nushell Nickel Plugin - CLI Wrapper Architecture

## Status

**Accepted** - 2025-12-15

## Context

The provisioning system integrates with Nickel for configuration management in advanced scenarios. Users need to evaluate Nickel files and work with their output in Nushell scripts. The `nu_plugin_nickel` plugin provides this integration.

The architectural decision was whether the plugin should:

1. **Implement Nickel directly using pure Rust** (`nickel-lang-core` crate)
2. **Wrap the official Nickel CLI** (`nickel` command)

### System Requirements

Nickel configurations in provisioning use the **module system**:

```nickel
# config/database.ncl
import "lib/defaults" as defaults
import "lib/validation" as valid

{
  databases: {
    primary = defaults.database & {
      name = "primary"
      host = "localhost"
    }
  }
}
```plaintext

Module system includes:

- Import resolution with search paths
- Standard library (`builtins`, stdlib packages)
- Module caching
- Complex evaluation context

## Decision

Implement the `nu_plugin_nickel` plugin as a **CLI wrapper** that invokes the external `nickel` command.

### Architecture Diagram

```plaintext
┌─────────────────────────────┐
│   Nushell Script            │
│                             │
│  nickel-export json /file   │
│  nickel-eval /file          │
│  nickel-format /file        │
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│   nu_plugin_nickel          │
│                             │
│  - Command handling         │
│  - Argument parsing         │
│  - JSON output parsing      │
│  - Caching logic            │
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│   std::process::Command     │
│                             │
│  "nickel export /file ..."  │
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│   Nickel Official CLI       │
│                             │
│  - Module resolution        │
│  - Import handling          │
│  - Standard library access  │
│  - Output formatting        │
│  - Error reporting          │
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│   Nushell Records/Lists     │
│                             │
│  ✅ Proper types            │
│  ✅ Cell path access works  │
│  ✅ Piping works            │
└─────────────────────────────┘
```plaintext

### Implementation Characteristics

**Plugin provides**:

- ✅ Nushell commands: `nickel-export`, `nickel-eval`, `nickel-format`, `nickel-validate`
- ✅ JSON/YAML output parsing (serde_json → nu_protocol::Value)
- ✅ Automatic caching (SHA256-based, ~80-90% hit rate)
- ✅ Error handling (CLI errors → Nushell errors)
- ✅ Type-safe output (nu_protocol::Value::Record, not strings)

**Plugin delegates to Nickel CLI**:

- ✅ Module resolution with search paths
- ✅ Standard library access and discovery
- ✅ Evaluation context setup
- ✅ Module caching
- ✅ Output formatting

## Rationale

### Why CLI Wrapper Is The Correct Choice

| Aspect | Pure Rust (nickel-lang-core) | CLI Wrapper (chosen) |
|--------|-------------------------------|----------------------|
| **Module resolution** | ❓ Undocumented API | ✅ Official, proven |
| **Search paths** | ❓ How to configure? | ✅ CLI handles it |
| **Standard library** | ❓ How to access? | ✅ Automatic discovery |
| **Import system** | ❌ API unclear | ✅ Built-in |
| **Evaluation context** | ❌ Complex setup needed | ✅ CLI provides |
| **Future versions** | ⚠️ Maintain parity | ✅ Automatic support |
| **Maintenance burden** | 🔴 High | 🟢 Low |
| **Complexity** | 🔴 High | 🟢 Low |
| **Correctness** | ⚠️ Risk of divergence | ✅ Single source of truth |

### The Module System Problem

Using `nickel-lang-core` directly would require the plugin to:

1. **Configure import search paths**:

   ```rust
   // Where should Nickel look for modules?
   // Current directory? Workspace? System paths?
   // This is complex and configuration-dependent
   ```

1. **Access standard library**:

   ```rust
   // Where is the Nickel stdlib installed?
   // How to handle different Nickel versions?
   // How to provide builtins?
   ```

2. **Manage module evaluation context**:

   ```rust
   // Set up evaluation environment
   // Configure cache locations
   // Initialize type checker
   // This is essentially re-implementing CLI logic
   ```

3. **Maintain compatibility**:
   - Every Nickel version change requires review
   - Risk of subtle behavioral differences
   - Duplicate bug fixes and features
   - Two implementations to maintain

### Documentation Gap

The `nickel-lang-core` crate lacks clear documentation on:

- ❓ How to configure import search paths
- ❓ How to access standard library
- ❓ How to set up evaluation context
- ❓ What is the public API contract?

This makes direct usage risky. The CLI is the documented, proven interface.

### Why Nickel Is Different From Simple Use Cases

**Simple use case** (direct library usage works):

- Simple evaluation with built-in functions
- No external dependencies
- No modules or imports

**Nickel reality** (CLI wrapper necessary):

- Complex module system with search paths
- External dependencies (standard library)
- Import resolution with multiple fallbacks
- Evaluation context that mirrors CLI

## Consequences

### Positive

- **Correctness**: Module resolution guaranteed by official Nickel CLI
- **Reliability**: No risk from reverse-engineering undocumented APIs
- **Simplicity**: Plugin code is lean (~300 lines total)
- **Maintainability**: Automatic tracking of Nickel changes
- **Compatibility**: Works with all Nickel versions
- **User Expectations**: Same behavior as CLI users experience
- **Community Alignment**: Uses official Nickel distribution

### Negative

- **External Dependency**: Requires `nickel` binary installed in PATH
- **Process Overhead**: ~100-200 ms per execution (heavily cached)
- **Subprocess Management**: Spawn handling and stderr capture needed
- **Distribution**: Provisioning must include Nickel binary

### Mitigation Strategies

**Dependency Management**:

- Installation scripts handle Nickel setup
- Docker images pre-install Nickel
- Clear error messages if `nickel` not found
- Documentation covers installation

**Performance**:

- Aggressive caching (80-90% typical hit rate)
- Cache hits: ~1-5 ms (not 100-200 ms)
- Cache directory: `~/.cache/provisioning/config-cache/`

**Distribution**:

- Provisioning distributions include Nickel
- Installers set up Nickel automatically
- CI/CD has Nickel available

## Alternatives Considered

### Alternative 1: Pure Rust with nickel-lang-core

**Pros**: No external dependency
**Cons**: Undocumented API, high risk, maintenance burden
**Decision**: REJECTED - Too risky

### Alternative 2: Hybrid (Pure Rust + CLI fallback)

**Pros**: Flexibility
**Cons**: Adds complexity, dual code paths, confusing behavior
**Decision**: REJECTED - Over-engineering

### Alternative 3: WebAssembly Version

**Pros**: Standalone
**Cons**: WASM support unclear, additional infrastructure
**Decision**: REJECTED - Immature

### Alternative 4: Use Nickel LSP

**Pros**: Uses official interface
**Cons**: LSP not designed for evaluation, wrong abstraction
**Decision**: REJECTED - Inappropriate tool

## Implementation Details

### Command Set

1. **nickel-export**: Export/evaluate Nickel file

   ```nushell
   nickel-export json /path/to/file.ncl
   nickel-export yaml /path/to/file.ncl
   ```

2. **nickel-eval**: Evaluate with automatic caching (for config loader)

   ```nushell
   nickel-eval /workspace/config.ncl
   ```

3. **nickel-format**: Format Nickel files

   ```nushell
   nickel-format /path/to/file.ncl
   ```

4. **nickel-validate**: Validate Nickel files/project

   ```nushell
   nickel-validate /path/to/project
   ```

### Critical Implementation Detail: Command Syntax

The plugin uses the **correct Nickel command syntax**:

```rust
// Correct:
cmd.arg("export").arg(file).arg("--format").arg(format);
// Results in: "nickel export /file --format json"

// WRONG (previously):
cmd.arg("export").arg(format).arg(file);
// Results in: "nickel export json /file"
// ↑ This triggers auto-import of nonexistent JSON module
```plaintext

### Caching Strategy

**Cache Key**: SHA256(file_content + format)
**Cache Hit Rate**: 80-90% (typical provisioning workflows)
**Performance**:

- Cache miss: ~100-200 ms (process fork)
- Cache hit: ~1-5 ms (filesystem read + parse)
- Speedup: 50-100x for cached runs

**Storage**: `~/.cache/provisioning/config-cache/`

### JSON Output Processing

Plugin correctly processes JSON output:

1. Invokes: `nickel export /file.ncl --format json`
2. Receives: JSON string from stdout
3. Parses: serde_json::Value
4. Converts: `json_value_to_nu_value()` (recursive)
5. Returns: nu_protocol::Value::Record (not string!)

This enables Nushell cell path access:

```nushell
nickel-export json /config.ncl | .database.host  # ✅ Works
```plaintext

## Testing Strategy

**Unit Tests**:

- JSON parsing correctness
- Value type conversions
- Cache logic

**Integration Tests**:

- Real Nickel file execution
- Module imports verification
- Search path resolution

**Manual Verification**:

```bash
# Test module imports
nickel-export json /workspace/config.ncl

# Test cell path access
nickel-export json /workspace/config.ncl | .database

# Verify output types
nickel-export json /workspace/config.ncl | type
# Should show: record, not string
```plaintext

## Configuration Integration

Plugin integrates with provisioning config system:

- Nickel path auto-detected: `which nickel`
- Cache location: platform-specific `cache_dir()`
- Errors: consistent with provisioning patterns

## References

- ADR-012: Nushell Plugins (general framework)
- [Nickel Official Documentation](https://nickel-lang.org/)
- [nickel-lang-core Rust Crate](https://crates.io/crates/nickel-lang-core/)
- nu_plugin_nickel Implementation: `provisioning/core/plugins/nushell-plugins/nu_plugin_nickel/`
- [Related: ADR-013-NUSHELL-KCL-PLUGIN](adr/adr-nushell-kcl-plugin-cli-wrapper.md)

---

**Status**: Accepted and Implemented
**Last Updated**: 2025-12-15
**Implementation**: Complete
**Tests**: Passing
