From c31f658672d6802587b932c2d86778d5884cd240 Mon Sep 17 00:00:00 2001 From: claude Date: Fri, 26 Dec 2025 12:59:22 -0300 Subject: [PATCH 1/4] docs: add Nushell code style guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract coding style patterns from git history (Sep 2024 onwards) to document Maxim's Nushell preferences for AI-assisted development. Covers pipeline composition, command choices, code structure, formatting conventions, and contrasts with Claude's style. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- STYLE.md | 477 ++++++++++++++++++ ...20251226-nushell-code-design-extraction.md | 70 +++ 2 files changed, 547 insertions(+) create mode 100644 STYLE.md create mode 100644 todo/20251226-nushell-code-design-extraction.md diff --git a/STYLE.md b/STYLE.md new file mode 100644 index 0000000..082da7f --- /dev/null +++ b/STYLE.md @@ -0,0 +1,477 @@ +# Nushell Code Design Guide + +This document captures Maxim Uvarov's Nushell coding style, extracted from the numd repository history (September 2024 onwards). Use it as a reference for maintaining consistency in AI-assisted development. + +## Table of Contents + +- [Pipeline Composition](#pipeline-composition) +- [Command Choices](#command-choices) +- [Code Structure](#code-structure) +- [Formatting Conventions](#formatting-conventions) +- [Maxim vs Claude: Style Contrasts](#maxim-vs-claude-style-contrasts) + +--- + +## Pipeline Composition + +### Leading Pipe Operator + +Place `|` at the start of continuation lines, left-aligned with `let`: + +```nushell +# Preferred +let row_type = $file_lines +| each { + str trim --right + | if $in =~ '^```' { } else { 'text' } +} +| scan --noinit 'text' {|curr prev| ... } + +# Avoid +let row_type = $file_lines | each { + str trim --right | if $in =~ '^```' { } else { 'text' } +} | scan --noinit 'text' {|curr prev| ... } +``` + +### Conditional Pass-Through with Empty `else { }` + +Use `| if ... { } else { }` idiom for conditional transformations that pass through unchanged otherwise: + +```nushell +# Pass through on false condition +| if $nu.os-info.family == windows { + str replace --all (char crlf) "\n" +} else { } + +# Multiple chained conditions +| if 'no-output' in $fence_options { return $in } else { } +| if 'separate-block' in $fence_options { generate-separate-block-fence } else { } +| if (can-append-print $in) { + generate-inline-output-pipeline + | generate-print-statement +} else { } +``` + +### `scan` for Stateful Transformations + +Use `scan --noinit` to process sequences with state: + +```nushell +# State machine for tracking fence context +| scan --noinit 'text' {|curr_fence prev_fence| + match $curr_fence { + 'text' => { if $prev_fence == 'closing-fence' { 'text' } else { $prev_fence } } + '```' => { if $prev_fence == 'text' { '```' } else { 'closing-fence' } } + _ => { $curr_fence } + } +} +``` + +### `window` for Adjacent Elements + +Use `window --remainder` to look at pairs/adjacent items: + +```nushell +| window --remainder 2 +| scan 0 {|window index| + if $window.0 == $window.1? { $index } else { $index + 1 } +} +``` + +### Building Tables with `wrap` and `merge` + +Construct tables column-by-column: + +```nushell +$file_lines | wrap line +| merge ($row_type | wrap row_type) +| merge ($block_index | wrap block_index) +| group-by block_index --to-table +| insert row_type { $in.items.row_type.0 } +| update items { get line } +| rename block_index line row_type +``` + +--- + +## Command Choices + +### Preferred Commands + +| Task | Preferred | Avoid | +|------|-----------|-------| +| Filtering | `where` | `filter` | +| Parallel with order | `par-each --keep-order` | `par-each` (when order matters) | +| Pattern dispatch | `match` expression | Long `if/else if` chains | +| Record iteration | `items {\|k v\| ...}` | Manual key extraction | +| Table grouping | `group-by ... --to-table` | Manual grouping | +| Line joining | `str join (char nl)` | `to text` (context dependent) | + +### `match` for Type/Pattern Dispatch + +```nushell +export def classify-block-action [ + $row_type: string +]: nothing -> string { + match $row_type { + 'text' => { 'print-as-it-is' } + '```output-numd' => { 'delete' } + + $i if ($i =~ '^```nu(shell)?(\s|$)') => { + if $i =~ 'no-run' { 'print-as-it-is' } else { 'execute' } + } + + _ => { 'print-as-it-is' } + } +} +``` + +### `items` for Record Iteration + +```nushell +$record | items {|k v| + $v + | str replace -r '^\s*(\S)' ' $1' + | str join (char nl) + | $"($k):\n($in)" +} +``` + +### Safe Navigation with `?` + +Use optional access for potentially missing fields: + +```nushell +$env.numd?.table-width? | default 120 +$env.numd?.prepend-code? +``` + +--- + +## Code Structure + +### Type Signatures + +Always include input/output type signatures: + +```nushell +export def clean-markdown []: string -> string { + ... +} + +export def parse-markdown-to-blocks []: string -> table, action: string> { + ... +} + +# Multiple return types (no commas) +export def run [ + file: path +]: [nothing -> string nothing -> nothing nothing -> record] { + ... +} +``` + +### @example Annotations + +Document functions with executable examples: + +```nushell +@example "generate marker for block 3" { + code-block-marker 3 +} --result "#code-block-marker-open-3" +export def code-block-marker [ + index?: int + --end +]: nothing -> string { + ... +} +``` + +### Semantic Action Labels + +Use meaningful labels instead of pattern matching throughout: + +```nushell +# Preferred: semantic labels +| where action == 'execute' +| where action != 'delete' + +# Avoid: repeated regex matching +| where row_type =~ '^```nu(shell)?(\s|$)' +``` + +### Const for Static Data + +Use `const` for lookup tables and static data: + +```nushell +const fence_options = [ + [short long description]; + + [O no-output "execute code without outputting results"] + [N no-run "do not execute code in block"] + [t try "execute block inside `try {}` for error handling"] + [n new-instance "execute block in new Nushell instance"] + [s separate-block "output results in separate code block"] +] + +export def list-fence-options []: nothing -> table { + $fence_options | select long short description +} +``` + +### Minimal Comments + +Code should be self-documenting. Use comments for "why", not "what": + +```nushell +# Preferred: explain non-obvious decisions +# I set variables here to prevent collecting $in var +let expanded_format = "\n```\n\nOutput:\n\n```\n" + +# Avoid: obvious comments +# This function cleans markdown +export def clean-markdown [] { ... } +``` + +--- + +## Formatting Conventions + +These follow Topiary formatter conventions. + +### Empty Blocks with Space + +```nushell +# Preferred +} else { } +| if $in == null { } else { str join (char nl) } + +# Avoid +} else {} +| if $in == null {} else { str join (char nl) } +``` + +### Closure Spacing + +Single-expression closures have spaces inside braces: + +```nushell +# Preferred +| update line { str join (char nl) } +| each { $in.items.row_type.0 } +| update metric { $'diff_($in)' } + +# Avoid +| update line {str join (char nl)} +| each {$in.items.row_type.0} +``` + +### Flag Spacing + +Space between long and short form: + +```nushell +# Preferred +--noinit (-n) +--restore (-r) + +# Avoid +--noinit(-n) +``` + +### Multi-line Records + +```nushell +# Preferred +return { + filename: $file + comment: "the script didn't produce any output" +} + +# Avoid +return { filename: $file, + comment: "the script didn't produce any output" } +``` + +### Long Command Parentheses + +```nushell +# Preferred +( + ^$nu.current-exe --env-config $nu.env-path --config $nu.config-path + --plugin-config $nu.plugin-path $intermed_script_path +) + +# Avoid +(^$nu.current-exe --env-config $nu.env-path --config $nu.config-path + --plugin-config $nu.plugin-path $intermed_script_path) +``` + +### Variable Declarations + +No `$` prefix on left-hand side of declarations: + +```nushell +# Preferred +let original_md = open -r $file +let row_type = $file_lines | each { ... } + +# Avoid (older style) +let $original_md = open -r $file +``` + +--- + +## Maxim vs Claude: Style Contrasts + +Understanding these differences helps maintain consistency. + +### Closure Parameters + +| Maxim | Claude | +|-------|--------| +| Named when helpful: `{\|block\| $block.line}` | Prefers `$in`: `{ $in.line }` | + +```nushell +# Maxim's style - named parameter for clarity +| each {|block| + if $block.block_index in $result_indices { + let result = $results | where block_index == $block.block_index + $block | update line { $result.line | lines } + } +} + +# Claude's style - $in idiom +| each { + if $in.block_index in $result_indices { + $in | update line { ($results | where block_index == $in.block_index).line | lines } + } +} +``` + +**Guideline**: Use named parameters when the closure body is complex or references the parameter multiple times. + +### Variable Naming + +| Maxim | Claude | +|-------|--------| +| Concise when scope is small | Always descriptive | + +```nushell +# Maxim's style - context makes meaning clear +| rename s f +| into int s f +let len = $longest_last_span_start - $last_span_end + +# Claude's style - self-documenting +| rename start end +| into int start end +let offset = $longest_last_span_start - $last_span_end +``` + +**Guideline**: Use concise names for local variables with small scope; be more descriptive for parameters and exports. + +### Helper Function Extraction + +| Maxim | Claude | +|-------|--------| +| Logic inline in main function | Extract named helpers | + +```nushell +# Maxim's style - inline logic +| if (check-print-append $in) { + create-indented-output + | generate-print-statement +} else { } + +# Claude's style - extracted helper +def apply-output-formatting [fence_options: list]: string -> string { + if 'no-output' in $fence_options { return $in } else { } + | if 'separate-block' in $fence_options { generate-separate-block-fence } else { } + | if (can-append-print $in) { + generate-inline-output-pipeline + | generate-print-statement + } else { } +} +``` + +**Guideline**: Keep logic inline unless it's reused or the function becomes too long. + +### Negation Syntax + +| Maxim | Claude | +|-------|--------| +| Sometimes `not ($x =~ ...)` | Prefers `$x !~ ...` | + +```nushell +# Both are acceptable, but prefer operator form +| where $it !~ '^# =>' # Preferred +| where not ($it =~ '^# =>') # Also acceptable +``` + +### Documentation + +| Maxim | Claude | +|-------|--------| +| Minimal, code speaks | Comprehensive docstrings | + +```nushell +# Maxim's style - brief or no docstring +export def clean-markdown []: string -> string { + str replace --all --regex "\n{3,}" "\n\n" + ... +} + +# Claude's style - detailed description +# Prettify markdown by removing unnecessary empty lines and trailing spaces. +# Handles empty output blocks, excess newlines, trailing whitespace, and +# ensures consistent file endings. +export def clean-markdown []: string -> string { + str replace --all --regex "\n```output-numd\\s+```\n" "\n" + ... +} +``` + +**Guideline**: Add docstrings for public API functions; internal helpers can be brief. + +### Commit Messages + +| Maxim | Claude | +|-------|--------| +| Descriptive phrases | Conventional commit format | + +``` +# Maxim's style +use `# =>` notation +indent output unconditionally +beautify + +# Claude's style +refactor: simplify closures using $in instead of named parameters +feat: add --ignore-git-check flag and error on uncommitted changes +fix: preserve existing $env.numd fields in load-config +``` + +--- + +## Quick Reference + +### Do + +- Start continuation lines with `|` +- Use empty `else { }` for pass-through +- Use `match` for type dispatch +- Use `scan` for stateful transforms +- Include type signatures +- Use `@example` annotations +- Use `const` for static data +- Keep functions focused + +### Don't + +- Over-extract helpers for one-time use +- Add excessive documentation for internal functions +- Use verbose names for local variables +- Break the pipeline flow unnecessarily +- Add comments for obvious code diff --git a/todo/20251226-nushell-code-design-extraction.md b/todo/20251226-nushell-code-design-extraction.md new file mode 100644 index 0000000..83b89c0 --- /dev/null +++ b/todo/20251226-nushell-code-design-extraction.md @@ -0,0 +1,70 @@ +# Task: Extract Nushell Code Design Style Guide + +**Created:** 2025-12-26 +**Status:** Completed + +## Objective + +Analyze git history to extract Maxim's personal Nushell coding style and design choices, creating a style guide document that preserves these aesthetics for future AI-assisted development. + +## Scope + +- **Time range:** September 2024 onwards (from tag 0.1.15) +- **Tags to analyze:** 0.1.15 → 0.1.16 → 0.1.17 → 0.1.18 → 0.1.19 → 0.1.20 → 0.1.21 → 0.2.0 → 0.2.1 → 0.2.2 + +## Focus Areas + +1. **Pipeline composition style** - How pipelines are structured, chained, and formatted +2. **Command choices** - Which Nushell commands are preferred for specific tasks +3. **Code structure and organization** - Module layout, function organization + +Note: Variable naming is acknowledged as not best-practice material (non-native English speaker perspective). + +## Output Format + +- Examples with explanations +- Contrast analysis: Maxim's style vs Claude's style +- Practical guidelines for maintaining consistency + +## Intended Use + +- Guide for Claude Code in future sessions +- Reference for Maxim's own coding + +## Analysis Strategy + +1. Start with diffs between tags (coarse-grained view) +2. Identify patterns in Maxim's commits vs Claude's commits +3. Extract concrete examples of style differences +4. Compile into Nushell-code-design.md + +## Tag Ranges to Analyze + +| From | To | Period | +|------|-----|--------| +| 0.1.15 | 0.1.16 | Aug 2024 - Feb 2025 | +| 0.1.16 | 0.1.17 | Feb 2025 | +| 0.1.17 | 0.1.18 | Feb 2025 | +| 0.1.18 | 0.1.19 | Feb - Mar 2025 | +| 0.1.19 | 0.1.20 | Mar 2025 | +| 0.1.20 | 0.1.21 | Mar - Nov 2025 | +| 0.1.21 | 0.2.0 | Nov - Dec 2025 | +| 0.2.0 | 0.2.1 | Dec 2025 | +| 0.2.1 | 0.2.2 | Dec 2025 | + +## Progress + +- [x] Analyze tag diffs with agents +- [x] Extract Maxim's style patterns +- [x] Identify Claude's style patterns +- [x] Document contrasts with examples +- [x] Create final Nushell-code-design.md + +## Output + +Created `/Users/user/git/numd/Nushell-code-design.md` with: +- Pipeline composition patterns +- Command choice preferences +- Code structure guidelines +- Formatting conventions (Topiary) +- Maxim vs Claude style contrasts with examples From 4ccee2f1a94190025970413983bcb472f506c231 Mon Sep 17 00:00:00 2001 From: claude Date: Fri, 26 Dec 2025 14:12:30 -0300 Subject: [PATCH 2/4] docs: simplify style guide to preference statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove Maxim vs Claude contrast framing, state preferences directly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- STYLE.md | 58 +++++++------------------------------------------------- 1 file changed, 7 insertions(+), 51 deletions(-) diff --git a/STYLE.md b/STYLE.md index 082da7f..3c5eb5a 100644 --- a/STYLE.md +++ b/STYLE.md @@ -129,7 +129,8 @@ export def classify-block-action [ ### `items` for Record Iteration ```nushell -$record | items {|k v| +$record +| items {|k v| $v | str replace -r '^\s*(\S)' ' $1' | str join (char nl) @@ -329,25 +330,17 @@ Understanding these differences helps maintain consistency. ### Closure Parameters -| Maxim | Claude | -|-------|--------| -| Named when helpful: `{\|block\| $block.line}` | Prefers `$in`: `{ $in.line }` | +Prefer `$in`: `{ $in.line }` + +Name when helpful: `{|block| $block.line}` ```nushell -# Maxim's style - named parameter for clarity | each {|block| if $block.block_index in $result_indices { let result = $results | where block_index == $block.block_index $block | update line { $result.line | lines } } } - -# Claude's style - $in idiom -| each { - if $in.block_index in $result_indices { - $in | update line { ($results | where block_index == $in.block_index).line | lines } - } -} ``` **Guideline**: Use named parameters when the closure body is complex or references the parameter multiple times. @@ -400,54 +393,17 @@ def apply-output-formatting [fence_options: list]: string -> string { ### Negation Syntax -| Maxim | Claude | -|-------|--------| -| Sometimes `not ($x =~ ...)` | Prefers `$x !~ ...` | +Prefer `$x !~ ...` over `not ($x =~ ...)` ```nushell -# Both are acceptable, but prefer operator form | where $it !~ '^# =>' # Preferred -| where not ($it =~ '^# =>') # Also acceptable ``` -### Documentation - -| Maxim | Claude | -|-------|--------| -| Minimal, code speaks | Comprehensive docstrings | - -```nushell -# Maxim's style - brief or no docstring -export def clean-markdown []: string -> string { - str replace --all --regex "\n{3,}" "\n\n" - ... -} - -# Claude's style - detailed description -# Prettify markdown by removing unnecessary empty lines and trailing spaces. -# Handles empty output blocks, excess newlines, trailing whitespace, and -# ensures consistent file endings. -export def clean-markdown []: string -> string { - str replace --all --regex "\n```output-numd\\s+```\n" "\n" - ... -} -``` - -**Guideline**: Add docstrings for public API functions; internal helpers can be brief. - ### Commit Messages -| Maxim | Claude | -|-------|--------| -| Descriptive phrases | Conventional commit format | +Use Conventional commit format ``` -# Maxim's style -use `# =>` notation -indent output unconditionally -beautify - -# Claude's style refactor: simplify closures using $in instead of named parameters feat: add --ignore-git-check flag and error on uncommitted changes fix: preserve existing $env.numd fields in load-config From 40c7ebbeb21bbe6aa6b261351ed3f11bde2af8e4 Mon Sep 17 00:00:00 2001 From: claude Date: Fri, 26 Dec 2025 14:25:41 -0300 Subject: [PATCH 3/4] docs: clarify pass-through pattern allows empty branch on either side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- STYLE.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/STYLE.md b/STYLE.md index 3c5eb5a..bf941ff 100644 --- a/STYLE.md +++ b/STYLE.md @@ -33,9 +33,9 @@ let row_type = $file_lines | each { } | scan --noinit 'text' {|curr prev| ... } ``` -### Conditional Pass-Through with Empty `else { }` +### Conditional Pass-Through with Empty `{ }` -Use `| if ... { } else { }` idiom for conditional transformations that pass through unchanged otherwise: +Use empty `{ }` for the branch that passes through unchanged: ```nushell # Pass through on false condition @@ -43,6 +43,11 @@ Use `| if ... { } else { }` idiom for conditional transformations that pass thro str replace --all (char crlf) "\n" } else { } +# Pass through on true condition +| if $echo { } else { + save -f $file +} + # Multiple chained conditions | if 'no-output' in $fence_options { return $in } else { } | if 'separate-block' in $fence_options { generate-separate-block-fence } else { } From ff23728c436eea2a0dc78767913c15ebfa965da6 Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 1 Jan 2026 00:14:30 -0300 Subject: [PATCH 4/4] docs: add data-first filtering and external command patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracted from version commit iterations: - Prefer data-first approach: define all data, then filter with `where` - Use `where` instead of `each {if} | compact` - Avoid spread operator with conditionals - Avoid unnecessary parentheses on external commands 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- STYLE.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/STYLE.md b/STYLE.md index bf941ff..3251d55 100644 --- a/STYLE.md +++ b/STYLE.md @@ -83,6 +83,47 @@ Use `window --remainder` to look at pairs/adjacent items: } ``` +### Data-First Filtering + +Define all data upfront, then filter. Prefer `where` over `each {if} | compact`: + +```nushell +# Preferred: data-first, filter with where +[ + [--env-config $nu.env-path] + [--config $nu.config-path] + [--plugin-config $nu.plugin-path] +] +| where {|i| $i.1 | path exists } +| flatten + +# Avoid: spread operator with conditionals +[ + ...(if ($nu.env-path | path exists) { [--env-config $nu.env-path] } else { [] }) + ...(if ($nu.config-path | path exists) { [--config $nu.config-path] } else { [] }) +] + +# Avoid: each + if + compact (use where instead) +| each {|i| if ($i.1 | path exists) { $i } } +| compact +``` + +### Pipeline Append vs Spread + +For conditional list building, prefer pipeline with `append` or data-first approach: + +```nushell +# Preferred: start empty, append conditionally +[] +| if $cond1 { append [a b] } else { } +| if $cond2 { append [c d] } else { } + +# Or: data-first with filtering (often cleaner) +[[a b] [c d]] +| where { some-condition $in } +| flatten +``` + ### Building Tables with `wrap` and `merge` Construct tables column-by-column: @@ -300,7 +341,21 @@ return { filename: $file, comment: "the script didn't produce any output" } ``` -### Long Command Parentheses +### External Command Parentheses + +Avoid unnecessary parentheses around external commands: + +```nushell +# Preferred +^$nu.current-exe ...$args $script +| complete + +# Avoid +(^$nu.current-exe ...$args $script) +| complete +``` + +For multi-line external commands, use parentheses with proper formatting: ```nushell # Preferred @@ -424,6 +479,8 @@ fix: preserve existing $env.numd fields in load-config - Use empty `else { }` for pass-through - Use `match` for type dispatch - Use `scan` for stateful transforms +- Use `where` for filtering (not `each {if} | compact`) +- Define data first, then filter - Include type signatures - Use `@example` annotations - Use `const` for static data @@ -431,6 +488,8 @@ fix: preserve existing $env.numd fields in load-config ### Don't +- Use spread operator `...` with conditionals (use data-first + `where`) +- Wrap external commands in unnecessary parentheses - Over-extract helpers for one-time use - Add excessive documentation for internal functions - Use verbose names for local variables