nu_plugin_nw_ulid 0.2.0

Production-grade ULID (Universally Unique Lexicographically Sortable Identifier) utilities plugin for Nushell with cryptographically secure operations, enterprise-grade security, and streaming support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
# Style Guide

Coding conventions for the nu_plugin_nw_ulid project. Each item has a unique ID for easy reference.

## STYLE-0000: Style guide structure

**Tags:** `meta`

### Situation

A new convention needs to be added to this style guide.

### Guidance

Assign the next sequential ID (currently next is `STYLE-0018`) and include:

1. A **Tags** line immediately after the heading — a comma-separated list of category labels
   from the tag vocabulary below.
2. Three subheadings:
   - **Situation** — when this rule applies
   - **Guidance** — what to do (with examples where helpful)
   - **Motivation** — why this rule exists

**Tag vocabulary** (extend as needed):

| Tag                  | Covers                                             |
|----------------------|----------------------------------------------------|
| `meta`               | Style guide structure and process                  |
| `error-handling`     | Error types, context messages, panics, suppression |
| `module-organization`| File layout, visibility, cohesion                  |
| `naming`             | Naming conventions for types, functions, files     |
| `commits`            | Commit message format, scope rules, discipline     |
| `documentation`      | Doc comments, examples                             |
| `testing`            | Test structure, fixtures, snapshots                |
| `code-style`         | Imports, clippy, constants, function length        |
| `api-design`         | Ownership, must_use, string params                 |
| `dependencies`       | Dependency selection and management                |
| `unsafe`             | Unsafe code policy                                 |
| `feature-scoping`    | Feature justification and scope discipline         |

A rule may have **multiple tags** — e.g., a rule about error messages in tests could be
tagged `error-handling, testing`.

Items are ordered by ID. **Do not** group items under section headings; use tags for
categorisation instead.

### Motivation

Consistent structure makes the guide scannable, and stable IDs allow code review comments
and ADRs to reference specific rules unambiguously. Tags replace section headings so that
items can remain in strict ID order without needing to be shuffled between sections when
categories overlap or new categories are introduced.

---

## STYLE-0001: Panicking operations

**Tags:** `error-handling`

### Situation

Considering `unwrap()`, `expect()`, or other panicking calls.

### Guidance

**`unwrap()` and `expect()` are acceptable** in **test code only**:

```rust
#[test]
fn test_generate_ulid() {
    let ulid = UlidEngine::generate().expect("Should generate ULID");
    assert!(UlidEngine::validate(&ulid.to_string()));
}
```

**In production code, use `?` with appropriate error conversion** for Nushell call methods
and engine operations:

```rust
// Good — propagates errors to the Nushell runtime
let count: Option<i64> = call.get_flag("count")?;
let ulid_str: String = call.req(0)?;
let components = UlidEngine::parse(&ulid_str)?;
```

**Use `if let` or `match` for fallible conversions** that return `Option`:

```rust
// Good — handles the None case gracefully
if let Some(datetime) =
    chrono::DateTime::from_timestamp(timestamp_secs as i64, timestamp_nanos as u32)
{
    record.push("iso8601", Value::string(datetime.format("...").to_string(), span));
}
```

**Never** use `unwrap()` or `expect()` on user-supplied or runtime data in library code.

### Motivation

Panics in plugin code crash the Nushell plugin host and produce poor diagnostics.
The `?` operator propagates errors as `LabeledError` values that Nushell can display
with span information, giving users actionable feedback instead of a panic backtrace.

---

## STYLE-0002: Naming patterns

**Tags:** `naming`

### Situation

Naming a new type, function, CLI command, or constant.

### Guidance

| Element           | Convention            | Examples                                      |
|-------------------|-----------------------|-----------------------------------------------|
| Structs / Enums   | PascalCase            | `UlidEngine`, `UlidError`, `SecurityWarnings` |
| Traits            | PascalCase (adj/verb) | `PluginCommand`, `Serialize`, `Display`       |
| Functions/Methods | snake_case            | `extract_timestamp()`, `validate()`           |
| Type aliases      | PascalCase            | `Result<T>` (for crate-local aliases)         |
| Constants         | UPPER_SNAKE_CASE      | `ULID_STRING_LENGTH`, `MAX_BULK_GENERATION`   |
| CLI commands      | kebab-case            | `ulid generate`, `ulid encode-base32`         |
| Modules / files   | snake_case            | `ulid_engine.rs`, `security.rs`               |

### Motivation

Standard Rust naming (`PascalCase` types, `snake_case` functions) is enforced by compiler
warnings and `clippy`. Kebab-case CLI commands follow Nushell conventions and are standard
across Unix tools.

---

## STYLE-0003: Commit message format

**Tags:** `commits`

### Situation

Writing a commit message.

### Guidance

Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:

```
<type>(<scope>): <description>

[optional body]

[optional footer(s)]
```

**Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `ci`, `perf`,
`build`.

**Scopes** (use the most specific that applies):

| Scope      | Covers                                        |
|------------|-----------------------------------------------|
| `cargo`    | Cargo build configuration and manifests       |
| `ci`       | CI/CD workflows and configuration             |
| `cli`      | CLI command methods and argument handling      |
| `commands` | Individual command implementations            |
| `config`   | Project configuration files and tooling setup |
| `core`     | Core types, constants, and engine internals   |
| `deps`     | Dependency updates                            |
| `docs`     | Documentation                                 |
| `engine`   | Core UlidEngine operations                    |
| `error`    | Error types and conversion                    |
| `lib`      | Library-level utilities and shared code       |
| `plugin`   | Plugin registration, command dispatch         |
| `release`  | Version bumps, release process                |
| `security` | Security warnings, rating system              |
| `style`    | Style guide rules and conventions             |
| `test`     | Test infrastructure and test utilities        |

**Multi-scope commits** — when a change touches multiple scopes equally, list them
comma-separated: `style(engine,commands): standardise doc comments`.

**Subject line rules:**

- Lowercase first word (no capital after the colon)
- No trailing period
- Imperative mood ("add feature" not "added feature")
- Under 72 characters total

### Motivation

Conventional commits produce machine-readable history that enables automated changelogs,
version bumping, and filtering by scope. Consistent subject lines make `git log --oneline`
scannable.

---

## STYLE-0004: Doc comments

**Tags:** `documentation`

### Situation

Adding or updating documentation on a module, type, or function.

### Guidance

**Module-level docs** — every module file starts with a `//!` comment:

```rust
//! Core ULID engine providing all ULID operations for the plugin.
```

**Item-level docs** — every public struct, enum, field, variant, and method gets `///`:

```rust
/// Parsed components of a ULID.
pub struct UlidComponents {
    /// ISO 8601 timestamp extracted from the ULID.
    pub timestamp: String,
    /// Hexadecimal representation of the random component.
    pub randomness_hex: String,
}
```

**Summary line style** — write in **third-person singular present indicative** per
[RFC 505](https://rust-lang.github.io/rfcs/0505-api-comment-conventions.html). Use full
sentences ending with a period:

```rust
/// Generates a new ULID with the current timestamp.
pub fn generate() -> Result<String, UlidError> { ... }

/// Returns `true` if the string is a valid ULID.
pub fn validate(input: &str) -> bool { ... }
```

| Correct (third-person)         | Incorrect (imperative)        |
|--------------------------------|-------------------------------|
| `/// Returns the length.`      | `/// Return the length.`      |
| `/// Creates a new client.`    | `/// Create a new client.`    |
| `/// Parses the input string.` | `/// Parse the input string.` |

### Motivation

The third-person convention matches the Rust standard library and `rustdoc` output, where
doc summaries read as descriptions of what the item *does* (e.g., `Vec::push` — "Appends
an element to the back of a collection."). RFC 505 codifies this as the official Rust API
documentation style.

---

## STYLE-0005: Import ordering

**Tags:** `code-style`

### Situation

Adding `use` statements to a file.

### Guidance

Group imports into three blocks separated by a blank line, in this order:

1. **Standard library** (`std`, `core`, `alloc`)
2. **External crates** (everything from `Cargo.toml` dependencies)
3. **Crate-internal** (`crate::`, `super::`, `self::`)

Within each group, let `cargo fmt` sort alphabetically.

```rust
use std::str::FromStr;

use nu_protocol::{Record, Span, Value};
use serde::{Deserialize, Serialize};
use ulid::Ulid;

use crate::security::SecurityWarnings;
use crate::UlidEngine;
```

**Enforcement note:** The rustfmt option `group_imports = "StdExternalCrate"` that codifies
this convention is still unstable. The three-group ordering is therefore a manual discipline
— `cargo fmt` will sort *within* a group but will not insert or enforce the blank-line
separators between groups. Review for this during code review.

### Motivation

Grouped imports make it easy to see at a glance what a module depends on externally versus
internally. The three-group convention is widely used in the Rust ecosystem. Alphabetical
ordering within groups is enforced by `cargo fmt`.

---

## STYLE-0006: Clippy configuration

**Tags:** `code-style`

### Situation

Configuring or overriding Clippy lints.

### Guidance

Run Clippy with `-D warnings` in CI so that lint violations fail the build.

When suppressing a lint on a specific item, use `#[allow(clippy::...)]` with a justification
comment explaining why the suppression is necessary:

```rust
#[allow(clippy::too_many_arguments)] // Builder pattern requires all fields at construction
fn new(title: &str, description: &str, ...) -> Self { ... }
```

Do not add blanket `#[allow(...)]` at module or crate level to silence warnings. Fix the
warning or suppress it at the narrowest possible scope.

### Motivation

`-D warnings` ensures lint violations are caught in CI. Requiring justification comments on
suppressions ensures each override is a deliberate decision rather than a way to silence
noise. Narrow-scope suppression prevents accidentally disabling a lint for unrelated code.

---

## STYLE-0007: Unsafe policy

**Tags:** `unsafe`, `code-style`

### Situation

Considering the use of `unsafe` code.

### Guidance

This project should not require `unsafe` code. If `unsafe` is ever needed, it must be:

1. Justified in an ADR
2. Isolated in a dedicated module
3. Annotated with a `// SAFETY:` comment per Clippy's `undocumented_unsafe_blocks` lint

### Motivation

This project has no need for `unsafe` — it delegates low-level operations to well-audited
dependencies (`ulid`, `chrono`). Requiring an ADR for any exception
ensures the decision is reviewed and documented.

---

## STYLE-0008: `#[must_use]` annotation

**Tags:** `api-design`

### Situation

A public function or method returns a computed value without side effects.

### Guidance

Apply `#[must_use]` to public functions whose return value is the entire point of the call.
Discarding the result is almost certainly a bug:

```rust
#[must_use]
pub fn validate(input: &str) -> bool { ... }

#[must_use]
pub fn is_security_sensitive_context(description: &str) -> bool { ... }
```

**Do not apply** `#[must_use]` to:

- Functions that return `Result` — the `#[must_use]` on `Result` itself already covers this.
- Functions with meaningful side effects (I/O, mutation) where the return value is
  supplementary.

### Motivation

`#[must_use]` turns silent logic errors (ignoring a return value) into compiler warnings.
Applying it deliberately to pure computations catches bugs at compile time without producing
false positives on side-effectful functions. This aligns with `clippy::must_use_candidate`
from the `pedantic` group.

---

## STYLE-0009: String parameter ownership

**Tags:** `api-design`

### Situation

Deciding whether a function parameter should be `&str`, `String`, or generic.

### Guidance

Use the cheapest type that satisfies the function's needs:

| The function…                       | Accept   | Example                                  |
|-------------------------------------|----------|------------------------------------------|
| Only reads the string               | `&str`   | `fn validate(input: &str) -> bool`       |
| Stores the string in a struct/`Vec` | `String` | `fn set_title(&mut self, title: String)` |

Prefer `&str` for engine methods and internal helpers. Since this project's public interface
is the Nushell plugin command layer (where `nu-protocol` types dictate parameter shapes), the
`&str` vs `String` decision applies primarily to internal function signatures.

For return types, prefer `&str` when returning a reference to owned data, and `String` when
returning a newly constructed value.

```rust
// Good — borrows for read-only access
pub fn timestamp(&self) -> &str {
    &self.timestamp
}

// Good — constructs a new string
pub fn format_summary(&self) -> String {
    format!("{}: {}", self.timestamp, self.randomness_hex)
}
```

### Motivation

Accepting `&str` avoids unnecessary allocations on the caller side. Taking `String` when
ownership is needed makes the transfer explicit and avoids hidden `.to_string()` calls
inside the function.

---

## STYLE-0010: Named constants

**Tags:** `code-style`, `naming`

### Situation

Using a numeric or string literal whose meaning is not obvious from surrounding context.

### Guidance

Extract **magic literals** into named constants or `const` items. A literal is "magic" when its
purpose is not self-evident at the usage site:

```rust
// Bad — what does 26 mean?
if input.len() != 26 {

// Good — the name documents the intent
const ULID_STRING_LENGTH: usize = 26;
if input.len() != ULID_STRING_LENGTH {
```

```rust
// Bad — why 10_000?
if count > 10_000 {
    return Err(...)
}

// Good
const MAX_BULK_GENERATION: usize = 10_000;
if count > MAX_BULK_GENERATION {
    return Err(...)
}
```

Literals that do **not** need extraction:

- **Structural zeros and ones**`Vec::with_capacity(1)`, `index + 1`, `slice[0]`.
- **Format strings**`format!("{}: {}", key, value)`.
- **Known-safe constructor arguments**`FixedOffset::east_opt(0)` (covered by STYLE-0001).
- **Test assertions**`assert_eq!(result.len(), 3)` where the value is local to the test.

Before defining a new constant, check whether one already exists for the same value — reuse it
rather than introducing a duplicate. Domain-wide constants (e.g., `ULID_STRING_LENGTH`,
`MS_PER_SECOND`) live as `pub const` in `ulid_engine.rs`; grep there first.

Place constants at the narrowest useful scope: module-level `const` if used across functions in
the same module, crate-level `pub const` in `ulid_engine.rs` if shared across modules, or
function-local `const` if truly local.

### Motivation

Named constants make the code self-documenting and provide a single point of change when a value
needs updating. Searching for `ULID_STRING_LENGTH` finds every usage; searching for `26` returns
hundreds of false positives. The exceptions prevent over-extraction of trivially obvious values.

---

## STYLE-0011: Function length

**Tags:** `code-style`

### Situation

Writing or reviewing a function that is growing long.

### Guidance

Keep functions **under ~50 lines** of logic (excluding doc comments, blank lines, and closing
braces). When a function exceeds this guideline, look for opportunities to extract coherent
sub-operations into well-named helper functions.

Common extraction targets:

- **Setup / teardown** — opening resources, building configuration structs.
- **Distinct phases** — validation, transformation, output formatting.
- **Repeated patterns** — similar blocks that differ only in parameters.
- **Nested closures** — iterator chains with complex transformation logic.

```rust
// Before — 120-line run() mixing validation, parsing, formatting, and output
fn run(&self, ...) -> Result<PipelineData, LabeledError> {
    // ... 120 lines ...
}

// After — orchestrator delegates to focused helpers
fn run(&self, ...) -> Result<PipelineData, LabeledError> {
    let input = self.parse_input(&call)?;
    let components = self.build_components(&input)?;
    let record = self.format_output(components, &call)?;
    Ok(Value::record(record, span).into_pipeline_data())
}
```

This is a **guideline, not a hard limit**. A 60-line function that reads linearly may be clearer
than three 20-line functions with non-obvious data flow. Use judgement — the goal is readability,
not a line count.

### Motivation

Long functions are harder to name, test, and review. Extracting sub-operations gives each piece
a name that serves as documentation and makes the top-level flow scannable. The ~50-line
heuristic is a common industry threshold (Clean Code, Effective Rust) that balances granularity
against fragmentation.

---

## STYLE-0012: Silent error suppression

**Tags:** `error-handling`

### Situation

Handling a `Result` or `Option` where the error/`None` case is intentionally ignored.

### Guidance

**Never silently discard an error that could indicate a real problem.** Three patterns to watch
for:

1. **`let _ = fallible_call();`** — If the operation can meaningfully fail, propagate with `?`
   or report via `eprintln!`. If the failure is truly inconsequential, add a comment
   explaining why:

   ```rust
   // Bad — error is silently swallowed
   let _ = call.get_flag::<String>("format");

   // Good — propagate to the Nushell runtime
   let format = call.get_flag::<String>("format")?;

   // Good — best-effort in a streaming context, documented
   // Item-level failure should not abort the entire stream.
   if let Err(e) = UlidEngine::parse(&input) {
       eprintln!("Skipping invalid ULID: {e}");
   }
   ```

2. **`if let Ok(x) = ... { use(x) }`** with no `else` — returning a silent default on parse
   or I/O failure hides broken data from the user:

   ```rust
   // Bad — silently returns 0 on parse failure
   let timestamp = UlidEngine::extract_timestamp(input).unwrap_or(0);

   // Good — the default is documented, or the error is surfaced
   let timestamp = match UlidEngine::extract_timestamp(input) {
       Ok(ts) => ts,
       Err(e) => {
           eprintln!("Failed to extract timestamp: {e}");
           0
       }
   };
   ```

3. **`.unwrap_or_default()` on non-trivial results** — acceptable for genuinely optional data,
   but not as a blanket substitute for error handling on operations that should succeed.

**Acceptable silent discards:**

- Best-effort error reporting when processing lists, where iteration should continue with
  remaining items.
- Optional metadata that may be absent and has a sensible default (e.g., timezone info from
  a parsed timestamp).

### Motivation

Silent error suppression is one of the hardest bugs to diagnose because nothing visibly fails —
the program simply produces wrong results or missing data. Reporting errors via `eprintln!`
costs nothing on the success path and provides a trail when something goes wrong. The explicit
comment requirement for `let _ =` forces the author to justify the suppression at write time,
which often reveals that the error should not be ignored after all.

---

## STYLE-0013: Single-purpose commits

**Tags:** `commits`

### Situation

Preparing a set of changes that involves refactoring, new functionality, or bug fixes.

### Guidance

Each commit should do **one kind of work**. Keep refactoring commits separate from
implementation commits, and both separate from bug-fix commits.

If a refactoring would make a subsequent implementation or fix cleaner, land the refactoring
as an **earlier** commit so that:

1. The refactoring can be reviewed on its own terms (no behaviour change expected).
2. The implementation commit starts from a cleaner baseline and is easier to understand.
3. Either commit can be reverted independently if needed.

```
# Good — reviewable, bisectable, revertible
git log --oneline
a1b2c3  refactor(engine): extract timestamp formatting helper
d4e5f6  feat(commands): add --json output to inspect command

# Bad — mixed intent, hard to review or revert half of it
git log --oneline
f7g8h9  feat(commands): add --json output and refactor timestamp formatting
```

**Acceptable exceptions:**

- Trivial renames or import cleanups that are a natural by-product of the implementation
  (a few lines, not a standalone refactoring effort).
- Prototype or spike branches where commit hygiene is deferred to a squash before merge.

### Motivation

Single-purpose commits make `git bisect` reliable, code review focused, and reverts
surgical. When refactoring is interleaved with behaviour changes, reviewers cannot tell
whether a difference is a deliberate new behaviour or a mechanical restructuring — so they
must verify every line as if it were new logic. Separating the two cuts review effort
roughly in half.

---

## STYLE-0014: Module cohesion

**Tags:** `module-organization`

### Situation

A source file is accumulating types, functions, or `impl` blocks that serve unrelated
purposes.

### Guidance

Each module should have a **single, nameable responsibility**. When you find it hard to
describe what a module does without using "and," it likely contains unrelated code that
would be clearer in separate modules.

**Signals that a module should be split:**

- It contains multiple independent command or handler types that share little or no private
  state (e.g., `UlidSortCommand` and `UlidInspectCommand` in one file).
- Unrelated sections require scanning past hundreds of lines to find the piece you need.
- Changes to one logical area routinely cause merge conflicts with work in another area of
  the same file.
- You struggle to name the file — broad names like `commands.rs` or `helpers.rs` suggest
  mixed responsibilities.

**What is *not* a reason to split:**

- Line count alone. A 400-line module with a single cohesive type and its helpers is fine.
- A few shared utility functions that genuinely serve every type in the module.

#### Command domain grouping

For plugin commands specifically, group by **command domain** — the shared subcommand noun
in the command name. Commands that share a domain noun typically share private helpers,
constants, and error-handling logic, making them cohesive within a single file.

For example, `ulid encode base32`, `ulid encode hex`, `ulid decode base32`, and
`ulid decode hex` all belong to the `encode` domain and live together in `encode.rs`.
A single-command domain like `ulid sort` still gets its own file (`sort.rs`).

The current domains in `src/commands/` are:

| File          | Domain   | Commands                                             |
|---------------|----------|------------------------------------------------------|
| `ulid.rs`     | core     | generate, validate, parse, security-advice           |
| `encode.rs`   | encode   | encode base32, decode base32, encode hex, decode hex |
| `time.rs`     | time     | time now, time parse, time millis                    |
| `uuid.rs`     | uuid     | uuid generate, uuid validate, uuid parse             |
| `sort.rs`     | sort     | sort                                                 |
| `inspect.rs`  | inspect  | inspect                                              |
| `info.rs`     | info     | info                                                 |

**When to create a new domain file vs. extending an existing one:**

- If a new command shares the subcommand noun of an existing domain, add it to that file.
- If a new command introduces a new noun (e.g., `ulid format …`), create a new file named
  after the noun (`format.rs`).
- Prefer domain-level files over one-file-per-command. A domain file with several related
  commands and their helpers is more navigable than many thin files that scatter shared
  context.

### Motivation

A module that mixes unrelated responsibilities is hard to navigate, produces noisy diffs,
and invites merge conflicts between independent work streams. Splitting by responsibility
makes each file's purpose obvious from its name, keeps diffs focused on the change at hand,
and lets reviewers evaluate one concern at a time. The emphasis on cohesion rather than a
rigid line limit avoids unnecessary churn on files that are large but focused, while still
flagging files that are large *because* they mix concerns.

Grouping commands by domain rather than putting all 23 commands in one file or giving each
its own file strikes a balance: related commands share helpers and constants without
cross-domain coupling, while the file tree remains compact enough to scan at a glance.

---

## STYLE-0015: Prefer standard library over external crates

**Tags:** `dependencies`

### Situation

Adding a new dependency, or a Rust release has stabilised functionality that an existing
dependency provides.

### Guidance

Before adding an external crate, check whether `std` already provides equivalent
functionality at the project's current MSRV. If it does, use `std`.

When a new Rust release absorbs a crate's functionality into `std` and the MSRV supports it,
migrate existing usage to the `std` equivalent and remove the external dependency.

This applies only where `std` provides a **genuine equivalent**. Domain-specific crates with
no `std` counterpart (e.g., `chrono`, `serde`, `blake3`, `ulid`) are unaffected.

```rust
// Good — std::sync::LazyLock (stable since 1.80.0)
use std::sync::LazyLock;

static PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"...").unwrap());

// Avoid — once_cell when std equivalent is available at MSRV
use once_cell::sync::Lazy;

static PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"...").unwrap());
```

If a crate's API is significantly more ergonomic or feature-rich than its `std` equivalent,
evaluate on a case-by-case basis whether the ergonomic benefit justifies the dependency cost.

### Motivation

Each external dependency carries costs: supply chain risk (see ADR-0002), compilation time,
upgrade burden, and version conflict surface. When `std` offers equivalent functionality,
these costs can be eliminated entirely. The Rust ecosystem follows a well-established pattern
of absorbing popular crate APIs into the standard library — aligning with this trend keeps
the dependency tree lean and reduces long-term maintenance.

---

## STYLE-0016: Error boundary conversion

**Tags:** `error-handling`

### Situation

A command calls an `UlidEngine` method that returns `Result<_, UlidError>`.

### Guidance

Engine functions return `UlidError` — they must not depend on `nu-protocol`. Commands
convert to `LabeledError` at the call boundary using `.map_err()` with the call span:

```rust
// Standard commands — return Result<PipelineData, LabeledError>
let components = UlidEngine::parse(&ulid_str)
    .map_err(|e| LabeledError::new("Parse failed")
        .with_label(e.to_string(), call.head))?;
```

Stream helpers that return `Result<_, Box<LabeledError>>` box the error instead:

```rust
// Stream helpers — return Result<_, Box<LabeledError>>
let ulid = UlidEngine::generate_with_timestamp(ts).map_err(|e| {
    Box::new(LabeledError::new("Generation failed")
        .with_label(e.to_string(), call_head))
})?;
```

The `LabeledError::new()` message should be a short category (e.g., "Parse failed",
"Generation failed"). The `.with_label()` carries the detailed `UlidError` display string
and the span for Nushell's error rendering.

### Motivation

Keeping `UlidError` free of `nu-protocol` means the engine can be tested and reused without
pulling in Nushell types. Converting at the command boundary — rather than inside the engine
or via a blanket `impl From` — makes the span available for error labels and keeps the
conversion visible at each call site. The pattern is consistent across call sites in
`ulid.rs`, `inspect.rs`, and `time.rs`.

---

## STYLE-0017: Feature justification

**Tags:** `feature-scoping`

### Situation

Proposing or implementing a new feature, command, flag, or module.

### Guidance

Every feature must solve a real problem for the user. Before building a feature, describe
a concrete user scenario where it provides value. If no such scenario exists, do not build
it.

**Design principles are not features.** Principles like security, performance, or
correctness describe *how* code should be written, not *what* to build. Apply them to the
code you are already writing:

- **Security:** do not introduce vulnerabilities (e.g., avoid `unsafe`, validate external
  input at system boundaries, use constant-time comparison where needed).
- **Performance:** choose efficient algorithms and data structures; avoid unnecessary
  allocations.
- **Correctness:** handle edge cases, propagate errors, write tests.

Do not create standalone features (new modules, commands, flags, or output fields) solely
to demonstrate compliance with a principle.

**Questions to ask before adding a feature:**

1. What user problem does this solve?
2. Can the user already solve it with existing commands or Nushell primitives?
3. Is the complexity proportional to the value delivered?

### Motivation

Vague principle-level directives (e.g., "prioritise security") can be misinterpreted as
feature requests, leading to invented functionality that provides no real value — such as
runtime keyword detection systems or advisory flags that no user asked for. This happened
in #69 and #91. Requiring a concrete user scenario as a prerequisite for every feature
prevents speculative artifacts and keeps the codebase focused on genuine user needs.