nushell-plugins/best_nushell_code.md
Jesús Pérez be62c8701a feat: Add ARGUMENTS documentation and interactive update mode
- Add `show-arguments` recipe documenting all version update commands
- Add `complete-update-interactive` recipe for manual confirmations
- Maintain `complete-update` as automatic mode (no prompts)
- Update `update-help` to reference new recipes and modes
- Document 7-step workflow and step-by-step differences

Changes:
- complete-update: Automatic mode (recommended for CI/CD)
- complete-update-interactive: Interactive mode (with confirmations)
- show-arguments: Complete documentation of all commands and modes
- Both modes share same 7-step workflow with different behavior in Step 4
2025-10-19 00:05:16 +01:00

770 lines
20 KiB
Markdown

# 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>] -> 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<int>]: 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
}