# Nushell Code Rules and Patterns for AI Agents ## Fundamental Rules for AI-Friendly Nushell Code ### Rule 1: One Command, One Purpose Every command must do exactly one thing. AI agents struggle with multi-purpose functions. ```nushell # ✅ GOOD - Single purpose def extract-errors [log_file: path] -> table { open $log_file | lines | parse "{time} [{level}] {msg}" | where level == "ERROR" } # ❌ BAD - Multiple purposes def process-logs [log_file: path, --extract-errors, --count-warnings, --save-output] { # Too many responsibilities } ``` ### Rule 2: Explicit Type Signatures Always AI agents need clear contracts. Never omit types. ```nushell # ✅ GOOD - Complete type information def calculate-average [numbers: list] -> float { $numbers | math avg } # ❌ BAD - Missing type information def calculate-average [numbers] { $numbers | math avg } ``` ### Rule 3: Return Early, Fail Fast Check preconditions immediately. Don't nest error handling. ```nushell # ✅ GOOD - Early returns def process-file [path: path] -> table { if not ($path | path exists) { error make {msg: $"File not found: ($path)"} } if (ls $path | get size.0) > 100mb { error make {msg: "File too large"} } open $path | process-data } # ❌ BAD - Nested conditionals def process-file [path: path] -> table { if ($path | path exists) { if (ls $path | get size.0) <= 100mb { open $path | process-data } else { error make {msg: "File too large"} } } else { error make {msg: "File not found"} } } ``` ## Essential Patterns for AI Code Generation ### Pattern 1: Command Template Pattern Use this structure for EVERY command: ```nushell # [PURPOSE]: Single-line description of what this does # [INPUT]: Expected input format # [OUTPUT]: Output format # [EXAMPLE]: command-name "arg1" --flag value def command-name [ required_param: type # Description of parameter optional_param?: type # Optional parameter --flag: type = default # Flag with default value ] -> return_type { # Step 1: Validate inputs # validation code here # Step 2: Process data # processing code here # Step 3: Return result # return statement } ``` ### Pattern 2: Pipeline Stage Pattern Each pipeline stage must be self-contained and testable: ```nushell # ✅ GOOD - Clear pipeline stages def analyze-data [input: path] -> table { load-data $input | validate-schema | clean-missing-values | calculate-statistics | format-output } # Each stage is a separate function def load-data [path: path] -> table { open $path } def validate-schema [data: table] -> table { $data | where column1 != null } def clean-missing-values [data: table] -> table { $data | fill-null 0 } def calculate-statistics [data: table] -> table { $data | group-by category | aggregate } def format-output [data: table] -> table { $data | select relevant_columns } ``` ### Pattern 3: Error Context Pattern Always provide context for errors that AI can use to fix issues: in Nushell 0.108, try-catch with error parameter might not be supported when assigning to variables. ```nushell # ✅ GOOD - Detailed error context def parse-config [config_path: path] -> record { try { open $config_path | from json } catch {|e| error make { msg: $"Failed to parse config" label: { text: $"Invalid JSON at ($config_path)" span: (metadata $config_path).span } help: "Check JSON syntax with: open $config_path | from json" } } } ``` ### Pattern 4: Data Validation Pattern Validate data structure at boundaries: ```nushell # ✅ GOOD - Explicit validation def process-user-data [data: table] -> table { # Define expected schema let required_columns = ["id", "name", "email", "age"] let actual_columns = ($data | columns) # Validate columns exist for col in $required_columns { if $col not-in $actual_columns { error make {msg: $"Missing required column: ($col)"} } } # Validate data types $data | each {|row| if ($row.age | describe) != "int" { error make {msg: $"Invalid age type for id ($row.id)"} } $row } } ``` ## Critical Rules for AI Tool Integration ### Rule 4: No Side Effects in Functions Functions must be pure unless explicitly named as mutations: ```nushell # ✅ GOOD - Pure function def calculate-tax [amount: float, rate: float] -> float { $amount * $rate } # ✅ GOOD - Mutation clearly indicated def write-to-file! [data: any, path: path] -> nothing { $data | save $path null } # ❌ BAD - Hidden side effect def calculate-tax [amount: float, rate: float] -> float { let result = $amount * $rate $result | save "tax_log.txt" --append # Hidden side effect! $result } ``` ### Rule 5: Atomic Operations Every operation must be atomic - it either completely succeeds or completely fails: ```nushell # ✅ GOOD - Atomic operation def update-json-file [path: path, updates: record] -> nothing { # Read, modify, write as single operation let original = try { open $path } catch { {} } let updated = ($original | merge $updates) try { $updated | save $path } catch {|e| error make { msg: $"Failed to update ($path)" help: "Ensure file is writable and valid JSON" } } null } ``` ### Rule 6: Explicit Dependencies Never rely on global state or environment without declaring it: ```nushell # ✅ GOOD - Explicit dependencies def api-request [ endpoint: string --api-key: string = "" # Will use env if not provided --base-url: string = "" ] -> any { let key = if $api_key == "" { $env.API_KEY } else { $api_key } let url = if $base_url == "" { $env.BASE_URL } else { $base_url } http get $"($url)/($endpoint)" --headers {Authorization: $"Bearer ($key)"} } # ❌ BAD - Hidden dependencies def api-request [endpoint: string] -> any { http get $"($env.BASE_URL)/($endpoint)" --headers {Authorization: $"Bearer ($env.API_KEY)"} } ``` ## Structured Data Patterns ### Pattern 5: Table Transformation Pattern Always use Nushell's table operations instead of loops: ```nushell # ✅ GOOD - Table operations def transform-sales [sales: table] -> table { $sales | insert quarter {|row| ($row.date | date quarter)} | insert profit {|row| $row.revenue - $row.cost} | group-by quarter | transpose quarter data | insert total {|row| $row.data | get profit | math sum} } # ❌ BAD - Manual iteration def transform-sales [sales: table] -> table { let result = [] for row in $sales { let quarter = ($row.date | date quarter) let profit = $row.revenue - $row.cost # Manual accumulation } $result } ``` ### Pattern 6: Schema Definition Pattern Define data schemas explicitly for AI understanding: ```nushell # Schema definitions at module level const USER_SCHEMA = { id: "int" name: "string" email: "string" created_at: "datetime" active: "bool" } const API_RESPONSE_SCHEMA = { status: "int" data: "table" error: "string?" # ? indicates optional } # Use schemas in functions def validate-user [user: record] -> bool { # Check against schema for key in ($USER_SCHEMA | columns) { if $key not-in ($user | columns) { return false } } true } ``` ## AI-Specific Patterns ### Pattern 7: Self-Documenting Code Pattern Include inline documentation that AI can parse: ```nushell def complex-calculation [ data: table # @format: [{x: float, y: float, weight: float}] ] -> record { # @returns: {mean: float, std: float, correlation: float} # @algorithm: Weighted Pearson correlation # @performance: O(n) time, O(1) space let weighted_mean_x = ( $data | reduce -f 0 {|row acc| $acc + ($row.x * $row.weight)} | $in / ($data | get weight | math sum) ) # ... rest of calculation # @output: Statistical measures { mean: $weighted_mean_x std: $std_dev correlation: $correlation } } ``` ### Pattern 8: Testable Unit Pattern Every function must include test examples: ```nushell # Function with embedded test cases def parse-version [version: string] -> record { # @test: "1.2.3" -> {major: 1, minor: 2, patch: 3} # @test: "2.0.0-beta" -> {major: 2, minor: 0, patch: 0} $version | parse "{major}.{minor}.{patch}" | get 0 | update major {|x| $x.major | into int} | update minor {|x| $x.minor | into int} | update patch {|x| $x.patch | into int} } # Separate test function def test-parse-version [] { assert ((parse-version "1.2.3") == {major: 1, minor: 2, patch: 3}) assert ((parse-version "2.0.0") == {major: 2, minor: 0, patch: 0}) } ``` ### Pattern 9: Incremental Computation Pattern Break complex computations into verifiable steps: ```nushell # ✅ GOOD - Each step is verifiable def analyze-dataset [data: path] -> record { # Load and get shape let dataset = (open $data) let shape = {rows: ($dataset | length), cols: ($dataset | columns | length)} print $"Loaded: ($shape.rows) rows, ($shape.cols) columns" # Check for missing values let missing = (check-missing $dataset) print $"Missing values: ($missing)" # Calculate statistics let stats = (calculate-stats $dataset) print $"Statistics calculated" # Generate report { shape: $shape missing: $missing statistics: $stats generated_at: (date now) } } ``` ## Module Organization Rules ### Rule 7: Single Responsibility Modules Each module handles one domain: ```nushell # file: data_validation.nu module data_validation { export def validate-email [email: string] -> bool { } export def validate-phone [phone: string] -> bool { } export def validate-date [date: string] -> bool { } } # file: data_transformation.nu module data_transformation { export def normalize-text [text: string] -> string { } export def parse-csv [path: path] -> table { } export def to-json [data: any] -> string { } } ``` ### Rule 8: Explicit Exports Only export what's needed: ```nushell module api_client { # Public API export def get [endpoint: string] -> any { make-request "GET" $endpoint } export def post [endpoint: string, data: any] -> any { make-request "POST" $endpoint $data } # Private helper - not exported def make-request [method: string, endpoint: string, data?: any] -> any { # Implementation } } ``` ## Performance Rules for AI Tools ### Rule 9: Lazy Evaluation Don't compute until necessary: ```nushell # ✅ GOOD - Lazy evaluation def process-conditionally [ data: table --expensive-analysis: bool = false ] -> any { # Quick return for common case if not $expensive_analysis { return ($data | first 10) } # Expensive computation only when needed $data | complex-analysis | generate-report } ``` ### Rule 10: Stream Large Data Never load entire large files into memory: ```nushell # ✅ GOOD - Streaming def process-large-file [path: path] -> nothing { open --raw $path | lines | each {|line| $line | parse-and-process } | save output.jsonl null } # ❌ BAD - Loading everything def process-large-file [path: path] -> table { let all_data = (open $path) # Loads entire file $all_data | process-all } ``` ## Error Handling Rules ### Rule 11: Never Swallow Errors Always propagate or handle errors explicitly: ```nushell # ✅ GOOD - Explicit error handling def safe-divide [a: float, b: float] -> float { if $b == 0 { error make {msg: "Division by zero"} } $a / $b } # ❌ BAD - Silent failure def safe-divide [a: float, b: float] -> float { if $b == 0 { 0 } else { $a / $b } # Hiding error! } ``` ### Rule 12: Structured Error Returns Use consistent error structures: ```nushell # Define error type const ERROR_SCHEMA = { success: "bool" error: "string?" data: "any?" timestamp: "datetime" } def api-call [url: string] -> record { try { let response = (http get $url) { success: true error: null data: $response timestamp: (date now) } } catch {|e| { success: false error: $e.msg data: null timestamp: (date now) } } } ``` ## Code Generation Rules for AI ### Rule 13: Predictable Naming Use consistent, predictable names: ```nushell # Naming conventions AI can predict: # - get-* : Returns data without modification # - set-* : Updates data # - is-* : Returns boolean # - has-* : Checks existence # - find-* : Searches for items # - create-* : Creates new items # - delete-* : Removes items # - update-* : Modifies existing items # - validate-*: Checks validity # - parse-* : Converts formats # - format-* : Outputs in specific format def get-user [id: int] -> record { } def is-valid-email [email: string] -> bool { } def find-duplicates [list: list] -> list { } def create-backup [path: path] -> path { } def parse-json [text: string] -> any { } ``` ### Rule 14: Consistent Parameter Order Always use this parameter order: ```nushell # Order: required positional, optional positional, flags def command [ required1: type # Required parameters first required2: type optional?: type # Optional parameters second --flag1: type # Boolean flags --flag2: type = value # Flags with defaults ] -> return_type { } ``` ### Rule 15: Return Type Consistency Use consistent return types for similar operations: ```nushell # All getters return record or null def get-config [] -> record? { try { open config.json } catch { null } } # All validators return bool def validate-data [data: any] -> bool { # validation logic true # or false } # All transformers return same type as input def transform-table [input: table] -> table { $input | modifications } ``` ### Rule 16: Function Signature Syntax with Pipeline Types (Nushell 0.107.1+) **CRITICAL**: When using return type annotations, use the pipeline signature: `[params]: input_type -> return_type` ```nushell # ✅ GOOD - Complete pipeline signature def process-data [input: string]: nothing -> table { $input | from json } def calculate-sum [numbers: list]: nothing -> int { $numbers | math sum } def check-status []: nothing -> bool { true } # ❌ BAD - Missing arrow (syntax error - will not parse!) def process-data [input: string]: table { $input | from json } # ⚠️ ALTERNATIVE - Omit return type entirely if unsure def process-data [input: string] { $input | from json } ``` **Note**: The syntax is `[parameters]: input_type -> return_type`. Both the colon AND arrow are required when specifying return types. This has been the syntax since Nushell 0.107.1. ### Rule 17: String Interpolation - Always Use Parentheses **CRITICAL**: In string interpolations, ALWAYS use parentheses `($var)` or `($expr)` for ALL interpolations. ```nushell # ✅ GOOD - Parentheses for variables print $"Processing file ($filename) at ($timestamp)" print $"Server ($hostname) running on port ($port)" print $"User ($username) has ($count) items" # ✅ GOOD - Parentheses for expressions and function calls print $"Total: (1 + 2 + 3)" print $"Date: (date now | format date '%Y-%m-%d')" print $"Size: ($data | length) items" # ❌ BAD - Square brackets DO NOT interpolate! print $"Processing file [$filename] at [$timestamp]" # Output: "Processing file [$filename] at [$timestamp]" (LITERAL!) # ❌ BAD - Without parentheses print $"Server $hostname on port $port" # This will cause a parse error ``` **Why**: - Parentheses `($var)` or `($expr)` are the ONLY way to interpolate in Nushell strings - Square brackets `[...]` are treated as literal characters (no interpolation) - Both variables and expressions use the same syntax: `($something)` - Consistent syntax reduces errors and improves maintainability **Rule of thumb**: - Variable? Use `($var)` - Expression? Use `($expr)` - Function call? Use `($fn arg)` - **NEVER** use square brackets for interpolation! ## Quick Reference Card for AI Agents ```nushell # TEMPLATE: Copy-paste this for new commands (Nushell 0.107.1+) # [PURPOSE]: # [INPUT]: # [OUTPUT]: # [EXAMPLE]: def command-name [ param: type # Description ]: nothing -> return_type { # NOTE: Use `: nothing -> return_type` for pipeline signature # Validate if VALIDATION_FAILS { error make {msg: $"Clear error message for ($param)"} # NOTE: ALWAYS use ($var) for string interpolation } # Process let result = $param | pipeline # Return $result } # COMMON PATTERNS # Load file: open $path # Save file: $data | save $path # Parse JSON: open $path | from json # Parse CSV: open $path | from csv # Filter table: $table | where column == value # Transform: $table | update column {|row| expression} # Group: $table | group-by column # Sort: $table | sort-by column # Select columns: $table | select col1 col2 # Check existence: $path | path exists # Get env: $env.VAR_NAME? | default "value" ``` ## Summary Checklist for AI-Compatible Nushell ✅ **Every function has:** - Explicit type signatures with pipeline syntax `[param: type]: input_type -> return_type {` - Single responsibility - Early validation - Clear error messages with `($var)` for ALL string interpolations - Test examples ✅ **Never use:** - Global state without declaration - Hidden side effects - Nested conditionals (prefer early returns) - Manual loops for table operations - Generic error messages - Try-catch with error parameter in variable assignments (use `do { } | complete`) - Square brackets `[$var]` for string interpolation (they don't work!) - Function signatures without both colon AND arrow when specifying return types ✅ **Always prefer:** - Pipeline operations over loops - Pure functions over stateful - Explicit over implicit - Composition over complexity - Streaming over loading - Parentheses `($var)` for ALL string interpolations (variables and expressions) ✅ **For AI tools specifically:** - Use predictable naming patterns - Include operation markers (! for mutations) - Document schemas inline - Provide test cases - Return consistent types - Follow Nushell 0.107.1+ syntax requirements (colon + arrow for return types) Following these rules and patterns ensures that AI agents like Claude Code can effectively read, understand, generate, and modify your Nushell code with high accuracy and reliability. 1. Try-Catch Block Pattern (10 files) - Issue: Nushell 0.108 has stricter parsing for try-catch blocks - Solution: Replace try {...} catch {...} with complete-based error handling: let result = (do { ... } | complete) if $result.exit_code == 0 { $result.stdout } else { error_value } - Files fixed: - workspace/version.nu - workspace/migration.nu (4 blocks) - user/config.nu - config/loader.nu - oci/client.nu (8 blocks - OCI currently disabled) 2. Function Signature Syntax (2 instances) - Issue: Missing input type in signatures - Old: def foo [x: string] -> bool - New: def foo [x: string]: nothing -> bool - Files fixed: workspace/helpers.nu 3. Boolean Flag Syntax (1 instance) - Issue: Type annotations not allowed on boolean flags - Old: --flag: bool = true - New: --flag = true - Files fixed: main_provisioning/contexts.nu 4. Variable Type Initialization (1 instance) - Issue: Can't assign record to null variable in 0.108 - Old: mut var = null; $var = {record} - New: mut var = {success: false}; $var = {record} #" Before (fails in Nushell 0.107.1) try { # operations result } catch { |err| log-error $"Failed: ($err.msg)" default_value } # After (works in Nushell 0.107.1) let result = (do { # operations result } | complete) if $result.exit_code == 0 { $result.stdout } else { log-error $"Failed: ($result.stderr)" default_value }