1 line
7.6 KiB
Markdown
1 line
7.6 KiB
Markdown
|
|
# ADR-001: KCL Plugin CLI Wrapper Architecture\n\n## Status\n\n**Accepted** - 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\n```kcl\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\n```plaintext\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\n**Core Functions** (`helpers.rs`):\n\n```rust\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\n**Plugin 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\n**Output 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\n**Caching** (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\n```rust\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
|