ADR-001: KCL Plugin CLI Wrapper Architecture\n\n## Status\n\nAccepted - 2025-12-15\n\n## Context\n\nThe nu_plugin_kcl project provides Nushell integration for KCL (KittyCAD Language). The core decision was whether to implement this as:\n\n1. Pure Rust Implementation (using kcl-lib crate directly)\n2. CLI Wrapper (using Command::new("kcl") to invoke external binary)\n\n### Technical Constraints\n\nKCL is a configuration language with module system:\n\n- Imports: import provisioning.lib as lib\n- Module resolution: kcl.mod files (similar to Go)\n- Standard library with external dependencies\n- Complex build context and validation\n\n### User Requirements\n\nAll KCL files in provisioning project use module system:\n\nkcl\n# workspace_librecloud/infra/sgoyol/settings.k\nimport provisioning.lib as lib\nimport provisioning.settings as cfg\n\n# Requires full module resolution\n\n\nThis means any KCL evaluation must:\n\n- ✅ Read and parse kcl.mod files\n- ✅ Resolve import paths (provisioning.lib → file path)\n- ✅ Access standard library\n- ✅ Handle all validation and compilation\n\n## Decision\n\nImplement nu_plugin_kcl as a CLI Wrapper that invokes the external kcl binary.\n\n### Architecture\n\nplaintext\nNushell Script\n ↓\nkcl-run (plugin command)\n ↓\nhelpers.rs: run_kcl_command()\n ↓\nstd::process::Command::new("kcl")\n ↓\nKCL CLI (official binary)\n ↓\nModule Resolution (guaranteed correct)\n ↓\nJSON Output\n ↓\nPlugin: serde_json::Value → nu_protocol::Value\n ↓\nNushell Records/Lists\n\n\n### Implementation Details\n\nCore Functions (helpers.rs):\n\nrust\npub(crate) fn run_kcl_command(\n file: &str,\n format: &str,\n output: Option<&str>,\n defines: Option<&str>,\n) -> Result<String>\n\n\nPlugin Commands (main.rs):\n\n1. kcl-run - Execute KCL files (input/output JSON, YAML)\n2. kcl-eval - Evaluate KCL with automatic caching\n3. kcl-format - Format KCL files\n4. kcl-validate - Validate KCL files/directories\n5. kcl-cache-status - Show cache information\n\nOutput Processing:\n\n- Invokes: kcl run /file.k --format json\n- Captures: stdout (JSON string)\n- Parses: serde_json::Value\n- Converts: json_value_to_nu_value() recursive function\n- Returns: nu_protocol::Value (records/lists, not strings)\n\nCaching (non-blocking, graceful degradation):\n\n- SHA256 content-addressed cache\n- Location: ~/.cache/provisioning/config-cache/\n- Key: SHA256(file_content + format + context)\n- Hit rate: Expected 80-90% in typical workflows\n\n### Type System\n\nCommand signatures declare Type::Any output:\n\nrust\n.input_output_type(Type::Any, Type::Any)\n\n\nThis allows:\n\n- Plugin returns: nu_protocol::Value::Record\n- Nushell receives: proper record (not string)\n- Cell path access works: kcl-run /file.k | .servers | length\n\n## Rationale\n\n### Why CLI Wrapper Over Pure Rust\n\n| Aspect | Pure Rust (kcl-lib) | CLI Wrapper (chosen) |\n|--------|-------------------|----------------------|\n| Module resolution | ❌ Unclear/Manual | ✅ Works automatically |\n| kcl.mod support | ❓ Undocumented | ✅ Guaranteed |\n| Import resolution | ❌ Need manual impl | ✅ Built-in |\n| Standard library | ❓ How to access? | ✅ Automatic |\n| Maintenance | ❌ Track CLI changes | ✅ No maintenance |\n| Error handling | ❌ Different from CLI | ✅ Same as CLI |\n| Complexity | 🔴 High | 🟢 Low |\n| External CLI | ✅ None needed | ✅ Requires kcl binary |\n\n### Why Not Pure Rust\n\nUsing kcl-lib directly would require:\n\n1. Implement module resolution from scratch:\n\n rust\n // Parse kcl.mod\n let mod_data = fs::read("kcl.mod")?;\n let config = parse_kcl_mod(&mod_data)?;\n\n // Resolve each import\n for import in file.imports {\n let path = resolve_module_path(&import, &config)?;\n }\n \n\n2. Handle standard library discovery:\n\n rust\n let stdlib_path = find_kcl_stdlib()?;\n // What version? Is it installed? Where?\n \n\n3. Replicate all CLI logic:\n - Error handling and messages\n - Validation rules\n - Module caching\n - Build context setup\n - Runtime options handling\n\nThis is essentially re-implementing the KCL CLI in Rust, which is:\n\n- ❌ Massive undertaking\n- ❌ Error-prone\n- ❌ High maintenance burden\n- ❌ Duplicates official implementation\n\n### Single Source of Truth\n\nDelegating to the CLI ensures:\n\n- ✅ Official implementation handles all edge cases\n- ✅ KCL updates automatically available\n- ✅ No maintenance as language evolves\n- ✅ Guaranteed correctness\n\n## Consequences\n\n### Positive\n\n- Correctness: Module resolution guaranteed correct by official CLI\n- Simplicity: No need to re-implement language internals\n- Maintenance: Updates to KCL automatically available\n- Features: All CLI features automatically supported\n- Compatibility: Works with all KCL versions\n- Reliability: Single point of truth (official implementation)\n- Error Messages: Same error handling as CLI users expect\n\n### Negative\n\n- External Dependency: Requires kcl binary in PATH\n- Performance Overhead: Process fork (~100-200ms vs direct call)\n- Process Management: Spawns subprocess for each execution\n- Error Output: Subprocess stderr handling required\n\n### Mitigations\n\nFor External Dependency:\n\n- Clear documentation: setup guide with kcl installation\n- Error messages: helpful if kcl not found\n- Distribution: kcl included in provisioning distributions\n\nFor Performance Overhead:\n\n- Caching: 80-90% hit rate in typical workflows\n- Cache hits: ~1-5ms (not 100-200ms)\n- Lazy evaluation: Only runs when needed\n\n## Alternatives Considered\n\n### Alternative 1: Pure Rust with kcl-lib\n\nRejected: Module resolution undefined, high maintenance cost\n\n### Alternative 2: Pure Rust with manual module implementation\n\nRejected: Duplicates official CLI, maintenance nightmare\n\n### Alternative 3: Hybrid (pure Rust + CLI fallback)\n\nRejected: Adds complexity, two implementations to maintain\n\n### Alternative 4: WebAssembly (kcl compiled to WASM)\n\nRejected: KCL WASM support unclear, adds complexity\n\n## Implementation Status\n\n### Completed\n\n- ✅ Plugin command infrastructure (5 commands)\n- ✅ CLI invocation via Command::new("kcl")\n- ✅ JSON output parsing (serde_json → nu_protocol)\n- ✅ Recursive value conversion (records, lists, primitives)\n- ✅ Caching system (SHA256, filesystem-based)\n- ✅ Error handling (CLI errors → Nushell errors)\n- ✅ Type system (Type::Any for proper output types)\n\n### Files\n\n- src/main.rs - Plugin commands and JSON parsing (95 lines of logic)\n- src/helpers.rs - CLI invocation and caching (300+ lines)\n- tests/ - Test suite for all commands\n\n## Testing\n\nManual Testing:\n\nbash\n# Test basic execution\nkcl-run /path/to/file.k -f json\n\n# Test with imports\nkcl-run /workspace_librecloud/infra/sgoyol/settings.k -f json | .servers\n\n# Test cache\nkcl-cache-status\n\n\nVerification:\n\n- ✅ Module imports resolve correctly\n- ✅ Output is proper records (not strings)\n- ✅ Cell path access works\n- ✅ Cache hits are fast\n- ✅ Error messages are helpful\n\n## References\n\n- KCL Official Documentation\n- kcl-lib Crate\n- Module System Design\n- Caching Strategy\n- JSON Output Format\n\n---\n\nAuthor: Architecture Team\nDate: 2025-12-15\nDecision Made By: Technical Review