fix: help system integration, build process optimization, and plugin rebuild efficiency ## Detailed Description This commit addresses critical issues in the help system discoverability, build process robustness, and plugin rebuild efficiency. ### 1. Help System Integration (New Feature) **Issue**: Version-update module recipes were not discoverable - Not shown in `just help modules` - Not referenced in `just help` - Not included in help navigation system - Users had to manually run `just --list` to find update commands **Solution**: - Added version-update module to all help outputs - Updated `justfiles/help.just` to document all 30+ version-update recipes - Created new `just commands` recipe as discoverable alias for `just --list` - Integrated version-update into help-all workflow **Impact**: - Version-update commands now fully discoverable via help system - Users can find update commands with: `just help modules`, `just help`, `just commands` - Improved overall help system navigation **Files Modified**: - `justfiles/help.just` (+23 lines) - Added version-update module to help sections - Added to modules list - Added to help-all workflow - New `commands` recipe showing all recipes by group ### 2. Build Process Fixes (Phase 3: Bin Archives) #### 2a. Plugin Archive Collection Bug **Issue**: "No plugins found to package" warning in Phase 3 - Collected 26 plugin binaries but reported 0 - Archive creation skipped because count was wrong **Root Cause**: `each` command returns null, so `| length` returned 0 ```nushell # ❌ OLD - each returns null let plugin_count = (ls nu_plugin_*/target/release/nu_plugin_* | each {|p| cp $p.name $"($temp_dir)/" } | length) # Returns 0! ``` **Solution**: Separated counting from copying with proper filtering ```nushell # ✅ NEW - count before operations let plugins_to_copy = (ls nu_plugin_*/target/release/nu_plugin_* | where type == "file") let plugin_count = ($plugins_to_copy | length) ``` **Impact**: - Now correctly collects and reports 26 plugins - Filters out .d dependency files automatically - Warning eliminated #### 2b. Tar Archive Path Handling **Issue**: Tar command failing silently with relative paths in subshell - `cd $temp_dir` changes context unpredictably - Relative path `../$archive_name` fails in subshell - Archive file not created despite exit code 0 **Root Cause**: Shell context and relative path issues in Nushell `do` block **Solution**: Used `tar -C` with absolute paths instead of `cd` ```nushell # ❌ OLD - unreliable context switching do { cd $temp_dir tar -czf ../$archive_name . } # ✅ NEW - absolute paths, no context switching tar -C $temp_dir -czf $archive_path . ``` **Additional Improvements**: - Absolute path construction using `pwd | path join` - Better error diagnostics with exit code and stderr output - File verification after creation **Impact**: - Tar archives now created successfully - Robust path handling across platforms - Clear error messages for debugging #### 2c. File Size Calculation Type Error **Issue**: Runtime error when calculating archive size ``` Error: The '/' operator does not work on values of type 'list<filesize>' ``` **Root Cause**: `ls` returns list of records, so `.size` was a list ```nushell # ❌ OLD - returns list<filesize> (ls $archive_path).size / 1024 / 1024 # ✅ NEW - returns filesize (ls $archive_path | get 0.size) / 1024 / 1024 ``` **Impact**: - Proper file size calculation in MB - No more type errors **Files Modified**: - `scripts/create_full_distribution.nu` (+58 lines, refactored plugin collection) - Fixed plugin counting logic - Improved path handling with absolute paths - Enhanced error diagnostics ### 3. Plugin Rebuild Optimization **Issue**: All plugins marked for rebuild even when dependencies unchanged - Step 4 (`update_all_plugins.nu`) touched all Cargo.toml files at 01:00:32 - Step 5 saw all files as "newer" than binaries - Marked ALL plugins for rebuild, though cargo only rebuilt changed ones **Root Cause**: Script always saved files, even when no changes made ```nushell # ❌ OLD - always saves, touching file timestamp $updated_content | to toml | save -f $cargo_toml ``` **Solution**: Only save if content actually changed ```nushell # ✅ NEW - compare before writing let original_toml = $content | to toml let new_toml = $updated_content | to toml if $original_toml != $new_toml { $updated_content | to toml | save -f $cargo_toml } ``` **Impact**: - Unchanged files preserve original timestamps - Only plugins with actual dependency changes are rebuilt - Efficient rebuild process with accurate file modification detection **Files Modified**: - `scripts/update_all_plugins.nu` (+12 lines, added content comparison) - Only touches files with real changes - Preserves timestamps for efficiency - Clearer logic and comments ### 4. Documentation **Files Modified**: - `CHANGELOG.md` (+56 lines) - Added comprehensive 2025-10-19 entry - Documented all fixes with root causes - Listed files modified and impact summary ## Technical Details ### Nushell Patterns Used 1. **Proper List Handling**: - `ls` returns list of records, access with `| get 0.size` - Filter with `where type == "file"` to exclude metadata 2. **Absolute Path Construction**: - `pwd | append "path" | path join` for cross-platform paths - Safer than string concatenation with `/` 3. **Content Comparison**: - Compare TOML string representation before saving - Preserves file timestamps for efficiency 4. **Error Diagnostics**: - Capture `stderr` from commands - Report exit codes and error messages separately ## Testing - [x] Help system shows version-update module - [x] `just commands` displays all recipes by group - [x] Phase 3 bin archive creation works - [x] Plugin collection reports correct count (26) - [x] Tar archives created successfully - [x] File size calculated correctly - [x] Plugin rebuild only touches changed files - [x] CHANGELOG updated with all changes ## Files Changed ``` 38 files changed, 2721 insertions(+), 2548 deletions(-) Core Changes: - justfiles/help.just (+23) Help system integration - scripts/create_full_distribution.nu (+58) Build process fixes - scripts/update_all_plugins.nu (+12) Rebuild optimization - CHANGELOG.md (+56) Documentation Dependency Updates: - All plugin Cargo.toml and Cargo.lock files (version consistency) ``` ## Breaking Changes None. These are bug fixes and optimizations that maintain backward compatibility. ## Migration Notes No migration needed. Improvements are transparent to users. ## Related Issues - Help system discoverability - Build process Phase 3 failures - Unnecessary plugin rebuilds - Build process reliability ## Checklist - [x] Changes follow Rust/Nushell idioms - [x] Code is well-commented - [x] Error handling is comprehensive - [x] Documentation is updated - [x] All changes tested - [x] No breaking changes introduced
808 lines
21 KiB
Markdown
808 lines
21 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!
|
|
|
|
### Rule 18: Escape Literal Parentheses in Interpolated Strings ⚠️
|
|
|
|
**CRITICAL**: In `$"..."` strings, parentheses that are NOT variable interpolation MUST be escaped.
|
|
|
|
❌ **WRONG**:
|
|
```nushell
|
|
let msg = $"Update? (yes/no): " # ERROR: (yes/no) treated as command
|
|
let text = $"Use format (JSON/YAML)" # ERROR: tries to execute command
|
|
```
|
|
|
|
✅ **CORRECT**:
|
|
```nushell
|
|
let msg = $"Update? \(yes/no\): " # Escaped literal parentheses
|
|
let text = $"Use format \(JSON/YAML\)" # Escaped literal parentheses
|
|
let msg = $"Value: ($var)" # Unescaped for variable interpolation
|
|
```
|
|
|
|
**Simple rule**:
|
|
- If `()` contains a variable name → keep as is: `($variable)`
|
|
- If `()` is literal text → escape with backslash: `\(text\)`
|
|
|
|
**Common mistake patterns:**
|
|
```nushell
|
|
# BAD - will try to execute 'yes/no' command
|
|
input $"Continue? (yes/no): "
|
|
|
|
# GOOD - escaped literal parentheses
|
|
input $"Continue? \(yes/no\): "
|
|
|
|
# BAD - will try to execute 'example' command
|
|
log_info $"Format (example): data"
|
|
|
|
# GOOD - escaped
|
|
log_info $"Format \(example\): data"
|
|
```
|
|
|
|
---
|
|
|
|
## 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
|
|
}
|