#!/usr/bin/env nu # audit-nu-scripts.nu - Automated Nushell script auditor for 0.109+ compatibility # Usage: nu .claude/guidelines/nushell/audit-nu-scripts.nu [--fix] [--verbose] def main [ --fix # Automatically fix simple issues --verbose # Show detailed output --output: string = "" # Output report to file ] { print_header # Find all Nushell scripts let scripts = glob **/*.nu | where {|path| not ($path | str starts-with "target/")} print $"Found ($scripts | length) Nushell script(s) to audit\n" mut issues = [] mut summary = { total_scripts: ($scripts | length) scripts_with_issues: 0 total_issues: 0 critical: 0 warning: 0 info: 0 } # Audit each script for script_path in $scripts { let script_issues = audit_script $script_path $verbose if ($script_issues | length) > 0 { $summary.scripts_with_issues = $summary.scripts_with_issues + 1 $summary.total_issues = $summary.total_issues + ($script_issues | length) # Count by severity for issue in $script_issues { match $issue.severity { "critical" => { $summary.critical = $summary.critical + 1 } "warning" => { $summary.warning = $summary.warning + 1 } "info" => { $summary.info = $summary.info + 1 } _ => {} } } # Add to issues list $issues = ($issues | append { script: $script_path issues: $script_issues }) } } # Print results print_divider print_summary $summary print_divider if ($issues | length) > 0 { print "\nšŸ“‹ DETAILED FINDINGS:\n" for script_result in $issues { print_script_issues $script_result.script $script_result.issues } } # Generate report if requested if $output != "" { generate_report $issues $summary $output print $"\nāœ“ Report saved to: ($output)" } # Exit code based on critical issues if $summary.critical > 0 { exit 1 } } # Audit a single script def audit_script [path: string, verbose: bool] -> list { if $verbose { print $"Auditing: ($path)" } mut issues = [] # Read script content let content = try { open --raw $path } catch { return [{ line: 0 severity: "critical" category: "file_access" message: $"Cannot read file: ($path)" suggestion: "Check file permissions" }] } let lines = $content | lines # Check 1: Missing shebang if (($lines | first) !~ "#!/usr/bin/env nu") { $issues = ($issues | append { line: 1 severity: "warning" category: "shebang" message: "Missing or incorrect shebang" suggestion: "Add: #!/usr/bin/env nu" }) } # Check 2: Old closure syntax (using $in in where/each) for (idx, line) in ($lines | enumerate) { let line_num = $idx + 1 # Check for old-style closures if ($line =~ '\| where \{ \$in') or ($line =~ '\| each \{ \$in') { $issues = ($issues | append { line: $line_num severity: "warning" category: "closure_syntax" message: "Old-style closure using $in" suggestion: "Use explicit parameter: {|x| $x ...} instead of { $in ... }" }) } # Check for missing type annotations on exported functions if ($line =~ 'export def .+\[') and not ($line =~ ': \w+') { $issues = ($issues | append { line: $line_num severity: "info" category: "type_annotations" message: "Function parameters missing type annotations" suggestion: "Add type annotations: [param: type]" }) } # Check for unwrap-like patterns (get without -i) if ($line =~ '\| get [a-zA-Z_]') and not ($line =~ 'get -i') { if not ($line =~ '\| get \w+ \|') { # Potentially unsafe get (not in pipeline continuation) $issues = ($issues | append { line: $line_num severity: "info" category: "null_safety" message: "Potentially unsafe 'get' without -i flag" suggestion: "Use 'get -i' for safe access or add error handling" }) } } # Check for hardcoded paths (common anti-pattern) if ($line =~ '/Users/[^/]+/') or ($line =~ 'C:\\\\') { $issues = ($issues | append { line: $line_num severity: "warning" category: "hardcoded_paths" message: "Hardcoded absolute path detected" suggestion: "Use $env.HOME or relative paths" }) } # Check for missing error handling in external commands if ($line =~ '\^[a-zA-Z_-]+') and not ($line =~ 'try \{') { # External command not wrapped in try-catch # Check if previous or next line has try let has_try = if $idx > 0 { ($lines | get ($idx - 1) | str contains "try") } else { false } if not $has_try { $issues = ($issues | append { line: $line_num severity: "info" category: "error_handling" message: "External command without error handling" suggestion: "Wrap in try-catch block" }) } } # Check for deprecated mut syntax if ($line =~ 'let mut ') { # This is actually fine in 0.109, just noting it # $issues = append with info if we want to track mutable usage } # Check for missing return type annotations if ($line =~ 'export def .+\]') and not ($line =~ '-> \w+') and not ($line =~ '\{$') { $issues = ($issues | append { line: $line_num severity: "info" category: "type_annotations" message: "Function missing return type annotation" suggestion: "Add return type: ] -> type {" }) } } # Check 3: No error handling in main functions let has_try = $content | str contains "try" let has_catch = $content | str contains "catch" if not ($has_try and $has_catch) { $issues = ($issues | append { line: 0 severity: "info" category: "error_handling" message: "Script may lack error handling (no try-catch found)" suggestion: "Add try-catch blocks for robust error handling" }) } $issues } # Print header def print_header [] { print "" print "╔════════════════════════════════════════════════════════╗" print "ā•‘ Nushell 0.109+ Compatibility Audit Tool ā•‘" print "ā•‘ syntaxis Project - Script Quality Checker ā•‘" print "ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•" print "" } # Print divider def print_divider [] { print "─────────────────────────────────────────────────────────" } # Print summary def print_summary [summary: record] { print "\nšŸ“Š AUDIT SUMMARY:\n" print $" Total scripts: ($summary.total_scripts)" print $" Scripts with issues: ($summary.scripts_with_issues)" print $" Total issues: ($summary.total_issues)" print "" print " By Severity:" print $" (ansi red)ā— Critical: ($summary.critical)(ansi reset)" print $" (ansi yellow)ā— Warnings: ($summary.warning)(ansi reset)" print $" (ansi cyan)ā— Info: ($summary.info)(ansi reset)" } # Print issues for a script def print_script_issues [script: string, issues: list] { print $"\n(ansi cyan)šŸ“„ ($script)(ansi reset)" for issue in $issues { let severity_icon = match $issue.severity { "critical" => $"(ansi red)āœ—(ansi reset)" "warning" => $"(ansi yellow)⚠(ansi reset)" "info" => $"(ansi cyan)ℹ(ansi reset)" _ => "•" } let line_info = if $issue.line > 0 { $" (Line ($issue.line))" } else { "" } print $" ($severity_icon) [($issue.category)]($line_info): ($issue.message)" print $" → ($issue.suggestion)" } } # Generate markdown report def generate_report [issues: list, summary: record, output_path: string] { let timestamp = date now | format date "%Y-%m-%d %H:%M:%S" mut report = $"# Nushell Script Audit Report Generated: ($timestamp) ## Summary - **Total Scripts**: ($summary.total_scripts) - **Scripts with Issues**: ($summary.scripts_with_issues) - **Total Issues**: ($summary.total_issues) - Critical: ($summary.critical) - Warnings: ($summary.warning) - Info: ($summary.info) ## Detailed Findings " for script_result in $issues { $report = $report + $"\n### ($script_result.script)\n\n" for issue in $script_result.issues { let line_ref = if $issue.line > 0 { $" (Line ($issue.line))" } else { "" } $report = $report + $"- **[($issue.severity | str upcase)] ($issue.category)**($line_ref) - Issue: ($issue.message) - Suggestion: ($issue.suggestion) " } } $report = $report + $"\n## Recommendations 1. Review all **critical** issues immediately 2. Address **warnings** before next release 3. Consider **info** items for code quality improvements 4. Follow guidelines in `.claude/guidelines/nushell/NUSHELL_0.109_GUIDELINES.md` --- *Generated by syntaxis Nushell Audit Tool* " $report | save --force $output_path } # Run main if executed directly main