From 8e934d89960b909eb6c45fb467fad28ffb37ae92 Mon Sep 17 00:00:00 2001 From: Andrew Moore Date: Tue, 11 Aug 2026 20:11:26 -0700 Subject: [PATCH] Merge nucleic/nimble-umber-toad-20h7 into dev --- NUCLEIC_FORK.md | 30 ++-- README.md | 16 +- brush-builtins/src/dot.rs | 2 +- brush-builtins/src/read.rs | 12 +- brush-core/src/commands.rs | 14 +- brush-core/src/completion.rs | 2 +- brush-core/src/expansion.rs | 28 +-- brush-core/src/gate.rs | 28 +-- brush-core/src/interp.rs | 222 ++++++++++++------------ brush-core/src/lib.rs | 2 +- brush-core/src/processes.rs | 8 +- brush-core/src/prompt.rs | 2 +- brush-shell/src/args.rs | 6 +- brush-shell/src/entry.rs | 6 +- brush-shell/src/productinfo.rs | 8 +- brush-shell/tests/cases/compat/ifs.yaml | 6 +- 16 files changed, 196 insertions(+), 196 deletions(-) diff --git a/NUCLEIC_FORK.md b/NUCLEIC_FORK.md index 5660200..3ee14f6 100644 --- a/NUCLEIC_FORK.md +++ b/NUCLEIC_FORK.md @@ -2,42 +2,42 @@ Vendored copy of [reubeno/brush](https://github.com/reubeno/brush), pinned at tag **`brush-shell-v0.4.0`** (upstream commit `96a26d0`), consumed by the -`shell/` workspace to build **nash**, the Nucleic agent shell -([docs/NASH.md](../../docs/NASH.md)). +`shell/` workspace to build **hash**, the Hydrangea agent shell +([docs/HYDRASHELL.md](../../docs/HYDRASHELL.md)). -Every fork change is marked with a `// nash:` comment. Current patches: +Every fork change is marked with a `// hydrashell:` comment. Current patches: | File | Change | | --- | --- | -| `brush-shell/src/productinfo.rs` | `PRODUCT_NAME` → `nash`; display string identifies the fork | +| `brush-shell/src/productinfo.rs` | `PRODUCT_NAME` → `hydrashell`; display string identifies the fork | | `brush-shell/src/args.rs` | usage/version strings rebranded | | `brush-core/src/prompt.rs` | `\$` (`PromptPiece::DollarOrPound`) renders the atom sign `⚛` (U+269B) for non-root instead of `$`; root still shows `#` | | `brush-core/src/expansion.rs` | corpus divergence D1: added `ExpansionPiece::LiteralText` — literal unquoted text is never field-split (bash splits only expansion results) but keeps glob characters active. Parameter-expansion substitutions (`${v:-word}` etc.) convert back to splittable at the expansion boundary; `ExpanderOptions.field_split_literal_text` lets data-string callers opt in. Upstream candidate. | | `brush-core/src/completion.rs` | `compgen -W` word list opts into `field_split_literal_text` (the -W string is data) | | `brush-shell/tests/cases/compat/ifs.yaml` | removed `known_failure` from 3 IFS tests the D1 fix repairs | | `brush-builtins/src/dot.rs` + `brush-shell/tests/cases/compat/builtins/dot.yaml` | the `.` / `source` builtin searches `PATH` for slashless operands when `sourcepath` is enabled, as Bash/POSIX require; fixes `git-subtree` sourcing `git-sh-setup` | -| `brush-core/src/gate.rs` (new) + `lib.rs` | the `Gate` trait (docs/NASH.md §4.2): process-global, verdict-shaped hook; allow-all default so an unconfigured shell behaves exactly like upstream | +| `brush-core/src/gate.rs` (new) + `lib.rs` | the `Gate` trait (docs/HYDRASHELL.md §4.2): process-global, verdict-shaped hook; allow-all default so an unconfigured shell behaves exactly like upstream | | `brush-core/src/commands.rs` | `SimpleCommand::execute` split into gate wrapper + `execute_inner`: `on_exec` before dispatch (Deny short-circuits), completion correlated through all three spawn-result variants | | `brush-core/src/processes.rs` | `ChildProcess.gate_token` + `on_exit` reporting from `wait()`/`poll()` (128+signal for signal deaths) | -| `brush-shell/src/entry.rs` | `set_exit_hook` seam so nash can flush its event buffer before `process::exit` | -| `brush-core/src/gate.rs` | M2 (docs/NASH.md §5.2–5.3): `RedirectRecord`/`RedirectReadback` + `ExecStart.redirects` + `Gate::on_cmdsub` default hook | -| `brush-core/src/interp.rs` | `ExecutionParameters.nash_redirects` accumulator; `setup_redirect` records file (`>`,`>>`,`<`,`2>`,`&>`,`<>`), heredoc, and here-string redirects | -| `brush-core/src/commands.rs` | gate wrapper drains `nash_redirects` into `ExecStart` for post-run read-back | +| `brush-shell/src/entry.rs` | `set_exit_hook` seam so hash can flush its event buffer before `process::exit` | +| `brush-core/src/gate.rs` | M2 (docs/HYDRASHELL.md §5.2–5.3): `RedirectRecord`/`RedirectReadback` + `ExecStart.redirects` + `Gate::on_cmdsub` default hook | +| `brush-core/src/interp.rs` | `ExecutionParameters.hydrashell_redirects` accumulator; `setup_redirect` records file (`>`,`>>`,`<`,`2>`,`&>`,`<>`), heredoc, and here-string redirects | +| `brush-core/src/commands.rs` | gate wrapper drains `hydrashell_redirects` into `ExecStart` for post-run read-back | | `brush-core/src/expansion.rs` | command-substitution arm calls `gate().on_cmdsub` with the trimmed output | -| `brush-core/src/gate.rs` | pipe tap (docs/NASH.md §5.3): `PipeEvent` + `Gate::on_pipe` default hook + `is_active()`/`next_pipeline_id()` gating | -| `brush-core/src/interp.rs` | `spawn_pipeline_processes` interposes a tee on each `a \| b` link when observation is active (`nash_spawn_pipe_tee`): a copier thread mirrors a bounded prefix, preserving backpressure and SIGPIPE, reports before closing the write end. No tee on the unobserved fast path. | +| `brush-core/src/gate.rs` | pipe tap (docs/HYDRASHELL.md §5.3): `PipeEvent` + `Gate::on_pipe` default hook + `is_active()`/`next_pipeline_id()` gating | +| `brush-core/src/interp.rs` | `spawn_pipeline_processes` interposes a tee on each `a \| b` link when observation is active (`hydrashell_spawn_pipe_tee`): a copier thread mirrors a bounded prefix, preserving backpressure and SIGPIPE, reports before closing the write end. No tee on the unobserved fast path. | Verification (this container): brush compat suite `2067 ran: 1684 succeeded, 6 failed, 377 known to fail, 38 skipped` — the 6 failures are identical to a pristine `brush-shell-v0.4.0` baseline run (environmental: ANSI-C quote and `echo -e` oracle differences, syntax-error formatting, missing `hexdump`), i.e. zero fork-caused regressions; +3 successes vs baseline are the repaired IFS -tests. nash corpus: 94/94. +tests. hash corpus: 94/94. -Planned (M3, per docs/NASH.md / NAROS.md): image-side delivery — nash baked and -forced on every Linux surface via narOS. No further brush-core patches expected +Planned (M3, per docs/HYDRASHELL.md / HYDRANGEAOS.md): image-side delivery — hash baked and +forced on every Linux surface via hydrangeaOS. No further brush-core patches expected for M3; enforcement (`Hold`/`Deny` verdicts) remains post-M6. -Updating: diff against the upstream tag, re-vendor, re-apply `// nash:` patches +Updating: diff against the upstream tag, re-vendor, re-apply `// hydrashell:` patches (grep for the marker), then re-run `shell/corpus/replay.py` and the brush compat suite (`cargo test -p brush-shell --test brush-compat-tests`). diff --git a/README.md b/README.md index f6541fa..684c428 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # nucleic-brush A fork of [**brush**](https://github.com/reubeno/brush) — the Bo(u)rn(e) RUsty SHell by -reuben olinsky — carrying the patches Nucleic needs to build **nash**, its agent shell. +reuben olinsky — carrying the patches Nucleic needs to build **hash**, its agent shell. Pinned at upstream tag **`brush-shell-v0.4.0`** (commit `96a26d0`). @@ -18,11 +18,11 @@ commands, their exits, and the data crossing pipes and redirections — from ins shell rather than by wrapping it. That is what these patches provide. Everything else is upstream brush, unmodified. -Every divergence carries a `// nash:` comment, so the complete diff against upstream is +Every divergence carries a `// hydrashell:` comment, so the complete diff against upstream is one grep away: ```console -$ grep -rn "nash:" --include="*.rs" --include="*.yaml" . +$ grep -rn "hydrashell:" --include="*.rs" --include="*.yaml" . ``` ### 1. The observation gate @@ -65,12 +65,12 @@ and belong upstream: ### 3. Branding -The binary this workspace builds is still named `brush`, but identifies itself as nash +The binary this workspace builds is still named `brush`, but identifies itself as hash so an agent-facing shell doesn't misrepresent what it is: | File | Change | | --- | --- | -| `brush-shell/src/productinfo.rs` | `PRODUCT_NAME` → `nash`; display string reads `nash (Nucleic agent shell, brush fork) …` | +| `brush-shell/src/productinfo.rs` | `PRODUCT_NAME` → `hydrashell`; display string reads `hydrashell (hash — Hydrangea agent shell, brush fork) …` | | `brush-shell/src/args.rs` | usage and version strings rebranded | | `brush-core/src/prompt.rs` | `\$` renders the atom sign `⚛` (U+269B) for non-root; root still shows `#` | @@ -80,8 +80,8 @@ current verification status. ## How it's consumed Nucleic's `shell/` workspace depends on `brush-shell` and `brush-parser` by path. The -`nash` binary is a thin wrapper that loads a root-owned operator policy, installs its -`Gate` implementation (`nash-observe`), flushes events at exit, and otherwise defers +`hydrashell` binary is a thin wrapper that loads a root-owned operator policy, installs its +`Gate` implementation (`hydrashell-observe`), flushes events at exit, and otherwise defers entirely to brush's bash-compatible CLI. None of the fork's crates are published; the `brush` binary built from here is a development artifact. @@ -105,7 +105,7 @@ ones a baseline run reproduces, plus the 3 IFS tests the expansion fix repairs. 1. Diff this tree against the pinned upstream tag. 2. Re-vendor the new tag. -3. Re-apply the fork patches — `grep -rn "nash:"` on the old tree enumerates all of +3. Re-apply the fork patches — `grep -rn "hydrashell:"` on the old tree enumerates all of them. 4. Re-run the brush compat suite and Nucleic's transcript-replay corpus. 5. Update the pin and the patch table in [`NUCLEIC_FORK.md`](NUCLEIC_FORK.md). diff --git a/brush-builtins/src/dot.rs b/brush-builtins/src/dot.rs index a53e796..92bfc2a 100644 --- a/brush-builtins/src/dot.rs +++ b/brush-builtins/src/dot.rs @@ -22,7 +22,7 @@ impl builtins::Command for DotCommand { context: brush_core::ExecutionContext<'_, SE>, ) -> Result { // TODO(dot): Handle trap inheritance. - // nash: POSIX requires `.` to search PATH when its operand has no slash. + // hydrashell: POSIX requires `.` to search PATH when its operand has no slash. // Git's `git-subtree` relies on this with `. git-sh-setup`; treating the // operand as cwd-relative leaves `require_work_tree` undefined. let script_path = Path::new(&self.script_path); diff --git a/brush-builtins/src/read.rs b/brush-builtins/src/read.rs index c30d7a0..fd3e487 100644 --- a/brush-builtins/src/read.rs +++ b/brush-builtins/src/read.rs @@ -298,16 +298,16 @@ struct InputReader { deadline: Option, /// Single-byte read buffer. /// - /// nash(D2): multi-byte UTF-8 sequences are buffered and decoded incrementally in + /// hydrashell(D2): multi-byte UTF-8 sequences are buffered and decoded incrementally in /// `read_event` (up to 4 bytes via `std::str::from_utf8`); invalid sequences degrade /// to per-byte Latin-1 chars (the pre-fix behavior for every byte). bash's `-n` /// counts bytes, which is preserved because the char-limit check measures /// `line.len()` — the accumulated String's UTF-8 byte length. buffer: [u8; 1], - /// nash(D2): a byte consumed while expecting a UTF-8 continuation that turned out to + /// hydrashell(D2): a byte consumed while expecting a UTF-8 continuation that turned out to /// start the next character; re-consumed before the stream is read again. pushback: Option, - /// nash(D2): decoded-but-undelivered chars from an invalid sequence flushed as + /// hydrashell(D2): decoded-but-undelivered chars from an invalid sequence flushed as /// Latin-1; drained before the stream is read again. pending: VecDeque, /// Terminal mode guard - kept alive for RAII cleanup on drop. @@ -356,7 +356,7 @@ impl InputReader { brush_core::sys::poll::poll_for_input(&self.input, Duration::ZERO).unwrap_or(false) } - /// nash(D2): reads one byte, honoring the pushback slot and the read deadline. + /// hydrashell(D2): reads one byte, honoring the pushback slot and the read deadline. /// Returns `Ok(None)` on EOF; timeout is reported via `TimedOut` in errors' stead by /// the caller checking the deadline, so this only polls when a deadline exists. fn read_byte(&mut self) -> Result, brush_core::Error> { @@ -372,10 +372,10 @@ impl InputReader { /// Reads the next input event, handling timeout and control characters. /// - /// nash(D2): decodes UTF-8 incrementally instead of casting each byte to `char` + /// hydrashell(D2): decodes UTF-8 incrementally instead of casting each byte to `char` /// (which was a Latin-1 decode and re-encoded input bytes — divergence D2 in /// shell/corpus/DIVERGENCES.md, found when ca-certificates' postinst mangled a - /// UTF-8 filename under narOS). Valid multi-byte sequences round-trip + /// UTF-8 filename under hydrangeaOS). Valid multi-byte sequences round-trip /// byte-identically through the accumulated String; invalid sequences degrade to /// the pre-fix per-byte Latin-1 mapping (a String cannot hold raw invalid bytes — /// full byte-fidelity for invalid UTF-8 would need Vec plumbing shell-wide). diff --git a/brush-core/src/commands.rs b/brush-core/src/commands.rs index 914838f..8cc2dca 100644 --- a/brush-core/src/commands.rs +++ b/brush-core/src/commands.rs @@ -349,7 +349,7 @@ impl<'a, SE: extensions::ShellExtensions> SimpleCommand<'a, SE> { /// executed command. This function's implementation is responsible for /// dispatching it appropriately according to the context provided. /// - /// nash: this is the gate choke point (docs/NASH.md §4.2). Every simple + /// hydrashell: this is the gate choke point (docs/HYDRASHELL.md §4.2). Every simple /// command passes through the process-global [`crate::gate::Gate`] before /// dispatch; completion is reported back with the issued token, however the /// command runs (inline, spawned process, or spawned task). @@ -362,13 +362,13 @@ impl<'a, SE: extensions::ShellExtensions> SimpleCommand<'a, SE> { if argv.is_empty() { argv.push(self.command_name.clone()); } - // nash: hand this command's redirects to the observer for post-run read-back. - let redirects = std::mem::take(&mut self.params.nash_redirects); + // hydrashell: hand this command's redirects to the observer for post-run read-back. + let redirects = std::mem::take(&mut self.params.hydrashell_redirects); let start_event = crate::gate::ExecStart { argv, cwd: self.shell.working_dir().to_path_buf(), redirects, - pipeline: self.params.nash_pipeline, + pipeline: self.params.hydrashell_pipeline, }; let token = match gate.on_exec(start_event) { crate::gate::Verdict::Allow(token) => token, @@ -836,11 +836,11 @@ pub(crate) async fn invoke_command_in_subshell_and_get_output( // Get our own set of parameters we can customize and use. let mut params = params.clone(); params.process_group_policy = ProcessGroupPolicy::SameProcessGroup; - // nash: a command substitution is an *argument* to the stage, not the stage itself, and it + // hydrashell: a command substitution is an *argument* to the stage, not the stage itself, and it // runs during expansion — i.e. before the stage's own command. Letting it inherit the slot // made `grep "$(printf x)" f | wc -l` report `printf x` as the producing stage - // (docs/NASH.md §5.1). - params.nash_pipeline = None; + // (docs/HYDRASHELL.md §5.1). + params.hydrashell_pipeline = None; // Set up pipe so we can read the output. let (reader, writer) = std::io::pipe()?; diff --git a/brush-core/src/completion.rs b/brush-core/src/completion.rs index de75a53..e6b01fe 100644 --- a/brush-core/src/completion.rs +++ b/brush-core/src/completion.rs @@ -308,7 +308,7 @@ impl Spec { // and field splitting but NOT pathname expansion (globbing). let options = crate::expansion::ExpanderOptions { pathname_expand: false, - // nash: the -W string is data — its literal text is split too. + // hydrashell: the -W string is data — its literal text is split too. field_split_literal_text: true, ..Default::default() }; diff --git a/brush-core/src/expansion.rs b/brush-core/src/expansion.rs index 74a3524..ecbd1b2 100644 --- a/brush-core/src/expansion.rs +++ b/brush-core/src/expansion.rs @@ -37,7 +37,7 @@ pub(crate) struct ExpanderOptions { /// Whether to perform pathname expansion (globbing). If disabled, glob patterns /// are returned as literal strings. pub pathname_expand: bool, - // nash: whether literal (unquoted, non-expansion) text is also subject to + // hydrashell: whether literal (unquoted, non-expansion) text is also subject to // field splitting. Off by default per POSIX/bash (only expansion results // split); enabled by callers whose input string is *data*, e.g. the // `compgen -W` word list. @@ -275,7 +275,7 @@ impl From for WordField { enum ExpansionPiece { Unsplittable(String), Splittable(String), - // nash: literal unquoted word text. Bash field-splits only the *results of + // hydrashell: literal unquoted word text. Bash field-splits only the *results of // expansions*, never literal text — but glob characters in literal text stay // active. This third state fixes `IFS=,; echo a,b,c` printing `a b c` // (corpus divergence D1) without breaking `echo *.txt`. @@ -297,7 +297,7 @@ impl From for patterns::PatternPiece { match piece { ExpansionPiece::Unsplittable(s) => Self::Literal(s), ExpansionPiece::Splittable(s) => Self::Pattern(s), - // nash: unquoted literal text keeps glob chars active. + // hydrashell: unquoted literal text keeps glob chars active. ExpansionPiece::LiteralText(s) => Self::Pattern(s), } } @@ -308,7 +308,7 @@ impl From for crate::regex::RegexPiece { match piece { ExpansionPiece::Unsplittable(s) => Self::Literal(s), ExpansionPiece::Splittable(s) => Self::Pattern(s), - // nash: unquoted literal text keeps pattern chars active. + // hydrashell: unquoted literal text keeps pattern chars active. ExpansionPiece::LiteralText(s) => Self::Pattern(s), } } @@ -335,12 +335,12 @@ impl ExpansionPiece { match self { Self::Unsplittable(_) => self, Self::Splittable(s) => Self::Unsplittable(s), - // nash: quoting literal text also deactivates its glob chars. + // hydrashell: quoting literal text also deactivates its glob chars. Self::LiteralText(s) => Self::Unsplittable(s), } } - // nash: opt literal text into field splitting (see + // hydrashell: opt literal text into field splitting (see // `ExpanderOptions::field_split_literal_text`). Quoted pieces stay intact. fn make_literal_splittable(self) -> Self { match self { @@ -547,7 +547,7 @@ struct WordExpander<'a, SE: extensions::ShellExtensions> { in_double_quotes: bool, /// Whether to use heredoc expansion semantics (literal quotes, no brace expansion). heredoc_mode: bool, - // nash: see `ExpanderOptions::field_split_literal_text`. + // hydrashell: see `ExpanderOptions::field_split_literal_text`. field_split_literal_text: bool, } @@ -684,7 +684,7 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> { &['$', '`', '\\', '\'', '\"', '~', '{'] }; if !word.contains(expansion_chars) { - // nash: a word with no expansion characters is pure literal text — + // hydrashell: a word with no expansion characters is pure literal text — // glob-active but not subject to field splitting. return Ok(Expansion::from(ExpansionPiece::LiteralText( word.to_owned(), @@ -821,7 +821,7 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> { // Go through the fields we have so far. for existing_field in expansion.fields { for piece in existing_field.0 { - // nash: callers whose input is data (e.g. compgen -W) opt + // hydrashell: callers whose input is data (e.g. compgen -W) opt // literal text into splitting. let piece = if self.field_split_literal_text { piece.make_literal_splittable() @@ -829,7 +829,7 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> { piece }; match piece { - // nash: literal text is never field-split (only expansion + // hydrashell: literal text is never field-split (only expansion // results are), but stays a distinct piece so its glob // characters remain active downstream. ExpansionPiece::Unsplittable(_) | ExpansionPiece::LiteralText(_) => { @@ -913,7 +913,7 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> { ) -> Result { let expansion: Expansion = match word_piece { brush_parser::word::WordPiece::Text(s) => { - // nash: literal word text — not field-split, glob-active. + // hydrashell: literal word text — not field-split, glob-active. Expansion::from(ExpansionPiece::LiteralText(s)) } brush_parser::word::WordPiece::SingleQuotedText(s) => { @@ -963,7 +963,7 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> { ExpansionPiece::Unsplittable(self.expand_tilde_expression(&tilde_expr)?), ), brush_parser::word::WordPiece::ParameterExpansion(p) => { - // nash: whatever a parameter expansion substitutes (including + // hydrashell: whatever a parameter expansion substitutes (including // literal text from ${v:-default} / ${v:+alt} words) is an // expansion *result*, so it is subject to field splitting. // Quoted pieces inside remain Unsplittable. @@ -999,7 +999,7 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> { let trimmed_len = cmd_output.trim_end_matches('\n').len(); cmd_output.truncate(trimmed_len); - // nash: capture command-substitution output (docs/NASH.md §5.3) — + // hydrashell: capture command-substitution output (docs/HYDRASHELL.md §5.3) — // invisible to the transcript, often carries decisions/secrets. if !self.disable_command_substitutions { crate::gate::gate().on_cmdsub(cmd_output.as_str()); @@ -1012,7 +1012,7 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> { // If we are *not* in a double-quoted context and we were requested to skip // unquoted backslash removal, then we need to skip removing backslashes here. if !self.in_double_quotes && self.disable_unquoted_backslash_removal { - // nash: retained-backslash text is literal, not an + // hydrashell: retained-backslash text is literal, not an // expansion result — no field splitting. return Ok(Expansion::from(ExpansionPiece::LiteralText(s))); } diff --git a/brush-core/src/gate.rs b/brush-core/src/gate.rs index 49404b7..295582f 100644 --- a/brush-core/src/gate.rs +++ b/brush-core/src/gate.rs @@ -1,9 +1,9 @@ -//! nash: the observation/enforcement gate (docs/NASH.md §4.2). +//! hydrashell: the observation/enforcement gate (docs/HYDRASHELL.md §4.2). //! //! A process-global, verdict-shaped hook at the shell's command choke point. //! The default implementation allows everything and observes nothing, so an -//! unconfigured shell behaves exactly like upstream brush. nash installs a -//! recording gate at startup (`nash-observe`); a future enforcement mode may +//! unconfigured shell behaves exactly like upstream brush. hash installs a +//! recording gate at startup (`hydrashell-observe`); a future enforcement mode may //! return `Deny` (and, later, hold pending a host policy round-trip) without //! any further changes to this crate. @@ -19,15 +19,15 @@ pub struct ExecStart { pub argv: Vec, /// The shell's working directory at execution time. pub cwd: PathBuf, - /// nash: the redirections applied to this command (docs/NASH.md §5.2). The + /// hydrashell: the redirections applied to this command (docs/HYDRASHELL.md §5.2). The /// observer reads back the affected byte range after the command completes. pub redirects: Vec, - /// nash: the pipeline stage this command occupies, when it runs as part of - /// `a | b | c` (docs/NASH.md §5.1). `None` for a standalone command. + /// hydrashell: the pipeline stage this command occupies, when it runs as part of + /// `a | b | c` (docs/HYDRASHELL.md §5.1). `None` for a standalone command. pub pipeline: Option, } -/// Where a command sits in the pipeline it belongs to (docs/NASH.md §5.1). +/// Where a command sits in the pipeline it belongs to (docs/HYDRASHELL.md §5.1). /// /// Shares its `id` with the [`PipeEvent`]s of the same pipeline, which is what /// lets an observer name the commands either side of a link (`curl … | sh`) @@ -42,7 +42,7 @@ pub struct PipelineSlot { pub len: usize, } -/// How a redirection's data is captured after the command runs (docs/NASH.md §5.2). +/// How a redirection's data is captured after the command runs (docs/HYDRASHELL.md §5.2). #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RedirectReadback { /// A truncating write (`>`, `>|`, `&>`): read the head of the final file. @@ -55,7 +55,7 @@ pub enum RedirectReadback { Inline, } -/// One redirection observed on a command. For regular files nash hands the child +/// One redirection observed on a command. For regular files hash hands the child /// the *real* fd (semantics untouched) and reads back a bounded range afterward; /// for heredoc/here-string the body is captured inline at setup. #[derive(Clone, Debug)] @@ -81,7 +81,7 @@ pub struct ExecEnd { pub exit_code: i32, } -/// Data observed flowing across one pipe link `a | b` (docs/NASH.md §5.3). +/// Data observed flowing across one pipe link `a | b` (docs/HYDRASHELL.md §5.3). /// Reported once the link drains (producer closed or consumer went away). #[derive(Clone, Debug)] pub struct PipeEvent { @@ -133,10 +133,10 @@ pub trait Gate: Send + Sync { /// Called when a simple command completes, with the token issued by /// `on_exec`. fn on_exit(&self, token: u64, ev: ExecEnd); - /// nash: called with the (trimmed) output of a command substitution - /// `$(…)` / backticks (docs/NASH.md §5.3). Default no-op. + /// hydrashell: called with the (trimmed) output of a command substitution + /// `$(…)` / backticks (docs/HYDRASHELL.md §5.3). Default no-op. fn on_cmdsub(&self, _output: &str) {} - /// nash: called once a pipe link `a | b` drains (docs/NASH.md §5.3). + /// hydrashell: called once a pipe link `a | b` drains (docs/HYDRASHELL.md §5.3). /// Invoked from the tee copier thread. Default no-op. fn on_pipe(&self, _ev: PipeEvent) {} } @@ -165,7 +165,7 @@ pub(crate) fn gate() -> &'static dyn Gate { } /// Whether a real (non-default) gate is installed. Pipe teeing — which adds a -/// thread + extra pipe per link — is only done when this is true (docs/NASH.md §5.3). +/// thread + extra pipe per link — is only done when this is true (docs/HYDRASHELL.md §5.3). pub(crate) fn is_active() -> bool { GATE.get().is_some() } diff --git a/brush-core/src/interp.rs b/brush-core/src/interp.rs index 8ae8812..2623378 100644 --- a/brush-core/src/interp.rs +++ b/brush-core/src/interp.rs @@ -37,16 +37,16 @@ pub struct ExecutionParameters { /// Whether `errexit` (exit on error) behavior should be /// suppressed in this execution context. Defaults to `false`. pub suppress_errexit: bool, - // nash: redirections applied to the command being built, drained into its - // `gate::ExecStart` for post-run read-back (docs/NASH.md §5.2). Empty unless + // hydrashell: redirections applied to the command being built, drained into its + // `gate::ExecStart` for post-run read-back (docs/HYDRASHELL.md §5.2). Empty unless // observation is active and the command has redirects. - pub(crate) nash_redirects: Vec, - // nash: the pipeline stage the command being built belongs to, stamped onto + pub(crate) hydrashell_redirects: Vec, + // hydrashell: the pipeline stage the command being built belongs to, stamped onto // its `gate::ExecStart` so the observer can name the stages either side of a - // pipe link (docs/NASH.md §5.1). Inherited by nested commands — a command + // pipe link (docs/HYDRASHELL.md §5.1). Inherited by nested commands — a command // inside a stage's subshell or brace group belongs to that same stage — and // overwritten only when a nested pipeline starts its own. - pub(crate) nash_pipeline: Option, + pub(crate) hydrashell_pipeline: Option, } impl ExecutionParameters { @@ -494,11 +494,11 @@ async fn spawn_pipeline_processes( let mut spawn_results = VecDeque::new(); let mut process_group_id: Option = None; - // nash: when observation is active, one id per multi-stage pipeline correlates + // hydrashell: when observation is active, one id per multi-stage pipeline correlates // both its tapped links and the exec events of the stages either side of them - // (docs/NASH.md §5.3). `None` on the unobserved fast path and for a lone + // (docs/HYDRASHELL.md §5.3). `None` on the unobserved fast path and for a lone // command, where there is no link to tap and nothing to correlate. - let nash_pipeline_id = if pipeline_len > 1 && crate::gate::is_active() { + let hydrashell_pipeline_id = if pipeline_len > 1 && crate::gate::is_active() { Some(crate::gate::next_pipeline_id()) } else { None @@ -508,9 +508,9 @@ async fn spawn_pipeline_processes( // command. if pipeline_len > 1 { for link in 0..(pipeline_len - 1) { - if let Some(pipeline_id) = nash_pipeline_id { - // nash: interpose a tee on the link so the bytes flowing `a | b` are - // captured (docs/NASH.md §5.3). + if let Some(pipeline_id) = hydrashell_pipeline_id { + // hydrashell: interpose a tee on the link so the bytes flowing `a | b` are + // captured (docs/HYDRASHELL.md §5.3). // Producer writes `a_write`; a copier moves bytes to `b_write`; // consumer reads `b_read`. Slots stay identical to the untapped // case, so the pop-ordering below is unchanged. @@ -521,13 +521,13 @@ async fn spawn_pipeline_processes( // Name both ends from their AST *here*, where the stage is unambiguous. Deriving // the name later from exec events cannot distinguish which command inside a // compound stage owned the pipe, and cannot name either end until it exits. - nash_spawn_pipe_tee( + hydrashell_spawn_pipe_tee( a_read, b_write, pipeline_id, from_index, - nash_stage_text(pipeline, from_index), - nash_stage_text(pipeline, from_index + 1), + hydrashell_stage_text(pipeline, from_index), + hydrashell_stage_text(pipeline, from_index + 1), ); pipe_readers.push(Some(openfiles::OpenFile::PipeReader(b_read))); pipe_writers.push(Some(openfiles::OpenFile::PipeWriter(a_write))); @@ -559,11 +559,11 @@ async fn spawn_pipeline_processes( // Set up parameters appropriate for this command. let mut cmd_params = params.clone(); - // nash: stamp this stage's slot so its exec event can be joined to the links + // hydrashell: stamp this stage's slot so its exec event can be joined to the links // either side of it. Only when this pipeline is itself tapped — a lone command // must keep any slot inherited from the stage that contains it. - if let Some(id) = nash_pipeline_id { - cmd_params.nash_pipeline = Some(crate::gate::PipelineSlot { + if let Some(id) = hydrashell_pipeline_id { + cmd_params.hydrashell_pipeline = Some(crate::gate::PipelineSlot { id, index: current_pipeline_index, len: pipeline_len, @@ -1728,11 +1728,11 @@ pub(crate) async fn setup_redirect( } let expanded_file_path = expanded_fields.remove(0); - // nash: `&>` / `&>>` writes stdout+stderr to one file — record fd 1 - // for read-back (docs/NASH.md §5.2). + // hydrashell: `&>` / `&>>` writes stdout+stderr to one file — record fd 1 + // for read-back (docs/HYDRASHELL.md §5.2). let abs = shell.absolute_path(Path::new(expanded_file_path.as_str())); let size_before = std::fs::metadata(&abs).map(|m| m.len()).unwrap_or(0); - params.nash_redirects.push(crate::gate::RedirectRecord { + params.hydrashell_redirects.push(crate::gate::RedirectRecord { op: if *append { "&>>".to_string() } else { "&>".to_string() }, fd: 1, path: Some(abs), @@ -1820,9 +1820,9 @@ pub(crate) async fn setup_redirect( ) })?; - // nash: record the redirect so the observer can read back the - // affected byte range after the command runs (docs/NASH.md §5.2). - nash_record_file_redirect(params, fd_num, kind, &expanded_file_path); + // hydrashell: record the redirect so the observer can read back the + // affected byte range after the command runs (docs/HYDRASHELL.md §5.2). + hydrashell_record_file_redirect(params, fd_num, kind, &expanded_file_path); params.open_files.set_fd(fd_num, opened_file); } @@ -1946,8 +1946,8 @@ pub(crate) async fn setup_redirect( let f = setup_open_file_with_contents(io_here_doc.as_str())?; - // nash: heredoc body is known at setup — capture inline (docs/NASH.md §5.2b). - nash_record_inline_redirect(params, fd_num, "<<", &io_here_doc); + // hydrashell: heredoc body is known at setup — capture inline (docs/HYDRASHELL.md §5.2b). + hydrashell_record_inline_redirect(params, fd_num, "<<", &io_here_doc); params.open_files.set_fd(fd_num, f); } @@ -1961,8 +1961,8 @@ pub(crate) async fn setup_redirect( let f = setup_open_file_with_contents(expanded_word.as_str())?; - // nash: here-string body is known at setup — capture inline. - nash_record_inline_redirect(params, fd_num, "<<<", &expanded_word); + // hydrashell: here-string body is known at setup — capture inline. + hydrashell_record_inline_redirect(params, fd_num, "<<<", &expanded_word); params.open_files.set_fd(fd_num, f); } @@ -1971,64 +1971,64 @@ pub(crate) async fn setup_redirect( Ok(()) } -// nash: bounded prefix captured per pipe link before reporting (docs/NASH.md §5.3). -const NASH_PIPE_CAPTURE_CAP: usize = 64 * 1024; +// hydrashell: bounded prefix captured per pipe link before reporting (docs/HYDRASHELL.md §5.3). +const HYDRASHELL_PIPE_CAPTURE_CAP: usize = 64 * 1024; -// nash: longest stage label carried on a pipe event. A stage can be an entire `while` loop, and -// this rides on every link of every pipeline — one of the highest-volume kinds nash emits. -const NASH_STAGE_TEXT_CAP: usize = 200; +// hydrashell: longest stage label carried on a pipe event. A stage can be an entire `while` loop, and +// this rides on every link of every pipeline — one of the highest-volume kinds hydrashell emits. +const HYDRASHELL_STAGE_TEXT_CAP: usize = 200; -// nash: the pipeline stage at `index`, rendered back to shell syntax to label a pipe link -// (docs/NASH.md §5.3). +// hydrashell: the pipeline stage at `index`, rendered back to shell syntax to label a pipe link +// (docs/HYDRASHELL.md §5.3). // // Collapsing and capping happen here rather than in the observer because this is a *label*, and // only brush-core has the AST to render one; redaction still happens observer-side with every // other outbound string. `Display` re-renders the parsed command, so the result is the stage as // parsed rather than byte-identical source — which is what a one-line label wants anyway. -fn nash_stage_text(pipeline: &ast::Pipeline, index: usize) -> String { +fn hydrashell_stage_text(pipeline: &ast::Pipeline, index: usize) -> String { let Some(command) = pipeline.seq.get(index) else { return String::new(); }; // A compound stage renders multi-line; a label has one line. let collapsed = command.to_string(); let mut text = collapsed.split_whitespace().collect::>().join(" "); - if text.chars().count() > NASH_STAGE_TEXT_CAP { - text = text.chars().take(NASH_STAGE_TEXT_CAP).collect::(); + if text.chars().count() > HYDRASHELL_STAGE_TEXT_CAP { + text = text.chars().take(HYDRASHELL_STAGE_TEXT_CAP).collect::(); text.push('…'); } text } -// nash: bytes moved per userspace copy on the portable tee path. 128 KiB rather than the 32 KiB +// hydrashell: bytes moved per userspace copy on the portable tee path. 128 KiB rather than the 32 KiB // this started with: the same stream costs a quarter of the read/write pairs, and a pipe link's // tee cost is almost entirely syscalls and the context switches around them -// (docs/NASH_STREAM_PERF_PLAN.md §P2.2). -const NASH_TEE_BUF: usize = 128 * 1024; +// (docs/HYDRASHELL_STREAM_PERF_PLAN.md §P2.2). +const HYDRASHELL_TEE_BUF: usize = 128 * 1024; -// nash: how large both ends of a tapped link are grown to, where the platform allows it. A tee +// hydrashell: how large both ends of a tapped link are grown to, where the platform allows it. A tee // puts *two* pipes where the shell asked for one, so a producer that outruns the default 64 KiB // buffer pays for it twice; growing them cuts the wakeups on both sides. #[cfg(target_os = "linux")] -const NASH_TEE_PIPE_SIZE: libc::c_int = 256 * 1024; +const HYDRASHELL_TEE_PIPE_SIZE: libc::c_int = 256 * 1024; -// nash: bytes asked of one `splice` call. The kernel moves at most a pipe-buffer's worth per call +// hydrashell: bytes asked of one `splice` call. The kernel moves at most a pipe-buffer's worth per call // regardless, so this only has to be comfortably larger than the pipe. #[cfg(target_os = "linux")] -const NASH_TEE_SPLICE_CHUNK: usize = 1024 * 1024; +const HYDRASHELL_TEE_SPLICE_CHUNK: usize = 1024 * 1024; -// nash: how many finished tee threads stay parked waiting for the next pipeline. Thread creation +// hydrashell: how many finished tee threads stay parked waiting for the next pipeline. Thread creation // is the bulk of the ~0.13 ms a tapped pipeline costs to *set up*, and shell-heavy builds run -// pipelines in the thousands (docs/NASH_STREAM_PERF_PLAN.md §P2.3). A shell runs its pipelines +// pipelines in the thousands (docs/HYDRASHELL_STREAM_PERF_PLAN.md §P2.3). A shell runs its pipelines // mostly one at a time, so a small pool covers the common case; a burst of concurrent pipelines // simply spawns beyond it and lets the extra workers retire when they finish. -const NASH_TEE_POOL_MAX_IDLE: usize = 4; +const HYDRASHELL_TEE_POOL_MAX_IDLE: usize = 4; -// nash: stack for a tee worker. It copies through a heap buffer and hands the gate an already-built +// hydrashell: stack for a tee worker. It copies through a heap buffer and hands the gate an already-built // event, so its own frames are shallow — a quarter of the 2 MiB default is ample and maps less. -const NASH_TEE_STACK: usize = 512 * 1024; +const HYDRASHELL_TEE_STACK: usize = 512 * 1024; -// nash: one link waiting to be copied — what a tee worker is handed. -struct NashTeeJob { +// hydrashell: one link waiting to be copied — what a tee worker is handed. +struct HydrashellTeeJob { src: std::io::PipeReader, dst: std::io::PipeWriter, pipeline_id: u64, @@ -2037,62 +2037,62 @@ struct NashTeeJob { to_text: String, } -// nash: senders of the tee workers currently parked, newest last. -static NASH_TEE_POOL: std::sync::Mutex>> = +// hydrashell: senders of the tee workers currently parked, newest last. +static HYDRASHELL_TEE_POOL: std::sync::Mutex>> = std::sync::Mutex::new(Vec::new()); -// nash: the process the parked workers belong to. A `fork` copies the pool's *memory* but none of +// hydrashell: the process the parked workers belong to. A `fork` copies the pool's *memory* but none of // its threads, so a child that inherited it would hand jobs to workers that do not exist — and a // dropped job silently breaks the pipeline it was tapping. Checked (and reset) on every dispatch. -static NASH_TEE_POOL_PID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); +static HYDRASHELL_TEE_POOL_PID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); -// nash: take a parked worker, if this process has one to spare. +// hydrashell: take a parked worker, if this process has one to spare. // // `try_lock`, never `lock`: this runs on the shell's own pipeline-setup path, so waiting on the // pool would put observation in front of a command — and after a `fork` the mutex can be left // locked by a thread that no longer exists. Failing to take a worker just means spawning one. -fn nash_tee_take_idle() -> Option> { - let mut pool = NASH_TEE_POOL.try_lock().ok()?; +fn hydrashell_tee_take_idle() -> Option> { + let mut pool = HYDRASHELL_TEE_POOL.try_lock().ok()?; let pid = std::process::id(); - if NASH_TEE_POOL_PID.swap(pid, std::sync::atomic::Ordering::Relaxed) != pid { + if HYDRASHELL_TEE_POOL_PID.swap(pid, std::sync::atomic::Ordering::Relaxed) != pid { pool.clear(); // inherited across a fork: those workers are not in this process } pool.pop() } -// nash: park a worker that just finished a link. Returns whether it was taken — a worker the pool +// hydrashell: park a worker that just finished a link. Returns whether it was taken — a worker the pool // has no room for (or cannot reach) simply exits. -fn nash_tee_park(tx: &std::sync::mpsc::Sender) -> bool { - let Ok(mut pool) = NASH_TEE_POOL.try_lock() else { +fn hydrashell_tee_park(tx: &std::sync::mpsc::Sender) -> bool { + let Ok(mut pool) = HYDRASHELL_TEE_POOL.try_lock() else { return false; }; - if pool.len() >= NASH_TEE_POOL_MAX_IDLE { + if pool.len() >= HYDRASHELL_TEE_POOL_MAX_IDLE { return false; } pool.push(tx.clone()); true } -// nash: a parked worker's loop — copy a link, park again, wait for the next one. -fn nash_tee_worker( - rx: &std::sync::mpsc::Receiver, - parked: &std::sync::mpsc::Sender, +// hydrashell: a parked worker's loop — copy a link, park again, wait for the next one. +fn hydrashell_tee_worker( + rx: &std::sync::mpsc::Receiver, + parked: &std::sync::mpsc::Sender, ) { while let Ok(job) = rx.recv() { - nash_run_pipe_tee(job); - if !nash_tee_park(parked) { + hydrashell_run_pipe_tee(job); + if !hydrashell_tee_park(parked) { return; } } } -// nash: hand a copier the link `src` (producer's output) → `dst` (consumer's input): it mirrors a -// bounded prefix, then reports the link to the gate (docs/NASH.md §5.3). The synchronous copy +// hydrashell: hand a copier the link `src` (producer's output) → `dst` (consumer's input): it mirrors a +// bounded prefix, then reports the link to the gate (docs/HYDRASHELL.md §5.3). The synchronous copy // preserves pipe backpressure; EOF on `src` or EPIPE on `dst` (consumer gone, e.g. `yes | head`) // ends it and closes `dst` so the consumer sees EOF. // `to_index` is always `from_index + 1` — a link joins adjacent stages — so it is derived rather -// than passed; `from_text`/`to_text` are the two stages already rendered by `nash_stage_text`. -fn nash_spawn_pipe_tee( +// than passed; `from_text`/`to_text` are the two stages already rendered by `hydrashell_stage_text`. +fn hydrashell_spawn_pipe_tee( src: std::io::PipeReader, dst: std::io::PipeWriter, pipeline_id: u64, @@ -2100,7 +2100,7 @@ fn nash_spawn_pipe_tee( from_text: String, to_text: String, ) { - let mut job = NashTeeJob { + let mut job = HydrashellTeeJob { src, dst, pipeline_id, @@ -2110,30 +2110,30 @@ fn nash_spawn_pipe_tee( }; // A worker that retired between being parked and being handed this job returns it, so try the // next one down rather than losing the link. - while let Some(tx) = nash_tee_take_idle() { + while let Some(tx) = hydrashell_tee_take_idle() { match tx.send(job) { Ok(()) => return, Err(std::sync::mpsc::SendError(returned)) => job = returned, } } - let (tx, rx) = std::sync::mpsc::channel::(); + let (tx, rx) = std::sync::mpsc::channel::(); let parked = tx.clone(); // If the spawn fails there is nobody to copy this link and the job is dropped, closing both // ends — the same outcome an unspawnable tee thread has always had, in a shell that is out of // threads either way. if std::thread::Builder::new() - .name("nash-pipe-tee".into()) - .stack_size(NASH_TEE_STACK) - .spawn(move || nash_tee_worker(&rx, &parked)) + .name("hydrashell-pipe-tee".into()) + .stack_size(HYDRASHELL_TEE_STACK) + .spawn(move || hydrashell_tee_worker(&rx, &parked)) .is_ok() { let _ = tx.send(job); } } -// nash: copy one link through to its consumer, then report it. -fn nash_run_pipe_tee(job: NashTeeJob) { - let NashTeeJob { +// hydrashell: copy one link through to its consumer, then report it. +fn hydrashell_run_pipe_tee(job: HydrashellTeeJob) { + let HydrashellTeeJob { mut src, mut dst, pipeline_id, @@ -2141,15 +2141,15 @@ fn nash_run_pipe_tee(job: NashTeeJob) { from_text, to_text, } = job; - nash_grow_pipe(&src); - nash_grow_pipe(&dst); + hydrashell_grow_pipe(&src); + hydrashell_grow_pipe(&dst); let mut captured: Vec = Vec::new(); let mut total: u64 = 0; // Only the prefix is ever copied through userspace; the rest of the stream — which is all of // it, for the transfers that actually cost something — is handed to the kernel. - if nash_tee_prefix(&mut src, &mut dst, &mut captured, &mut total) { - nash_tee_passthrough(&mut src, &mut dst, &mut total); + if hydrashell_tee_prefix(&mut src, &mut dst, &mut captured, &mut total) { + hydrashell_tee_passthrough(&mut src, &mut dst, &mut total); } // Report BEFORE closing `dst`: dropping it unblocks the consumer, which @@ -2169,17 +2169,17 @@ fn nash_run_pipe_tee(job: NashTeeJob) { drop(dst); } -// nash: copy until the capture cap is reached, mirroring what goes past. Returns whether the link +// hydrashell: copy until the capture cap is reached, mirroring what goes past. Returns whether the link // is still open (false on EOF, or once either end gives up). -fn nash_tee_prefix( +fn hydrashell_tee_prefix( src: &mut std::io::PipeReader, dst: &mut std::io::PipeWriter, captured: &mut Vec, total: &mut u64, ) -> bool { use std::io::{Read, Write}; - let mut buf = vec![0u8; NASH_TEE_BUF]; - while captured.len() < NASH_PIPE_CAPTURE_CAP { + let mut buf = vec![0u8; HYDRASHELL_TEE_BUF]; + while captured.len() < HYDRASHELL_PIPE_CAPTURE_CAP { let n = match src.read(&mut buf) { Ok(0) => return false, // producer closed → done Ok(n) => n, @@ -2187,7 +2187,7 @@ fn nash_tee_prefix( Err(_) => return false, }; *total += n as u64; - let room = NASH_PIPE_CAPTURE_CAP - captured.len(); + let room = HYDRASHELL_PIPE_CAPTURE_CAP - captured.len(); captured.extend_from_slice(&buf[..room.min(n)]); // Pass through; if the consumer went away, stop (its EOF is enough). if dst.write_all(&buf[..n]).is_err() { @@ -2197,11 +2197,11 @@ fn nash_tee_prefix( true } -// nash: the portable passthrough — the same read/write loop as the prefix phase, minus the +// hydrashell: the portable passthrough — the same read/write loop as the prefix phase, minus the // capture. Used on platforms without `splice`, and as the fallback when the kernel refuses it. -fn nash_tee_copy(src: &mut std::io::PipeReader, dst: &mut std::io::PipeWriter, total: &mut u64) { +fn hydrashell_tee_copy(src: &mut std::io::PipeReader, dst: &mut std::io::PipeWriter, total: &mut u64) { use std::io::{Read, Write}; - let mut buf = vec![0u8; NASH_TEE_BUF]; + let mut buf = vec![0u8; HYDRASHELL_TEE_BUF]; loop { let n = match src.read(&mut buf) { Ok(0) => return, @@ -2216,12 +2216,12 @@ fn nash_tee_copy(src: &mut std::io::PipeReader, dst: &mut std::io::PipeWriter, t } } -// nash: move the rest of the link pipe-to-pipe inside the kernel (docs/NASH_STREAM_PERF_PLAN.md +// hydrashell: move the rest of the link pipe-to-pipe inside the kernel (docs/HYDRASHELL_STREAM_PERF_PLAN.md // §P2.1). Past the captured prefix the tee has nothing to look at, so there is no reason for the // bytes to enter this process at all — and the byte count comes back from the same call that // moves them. #[cfg(target_os = "linux")] -fn nash_tee_passthrough( +fn hydrashell_tee_passthrough( src: &mut std::io::PipeReader, dst: &mut std::io::PipeWriter, total: &mut u64, @@ -2237,7 +2237,7 @@ fn nash_tee_passthrough( std::ptr::null_mut(), out_fd, std::ptr::null_mut(), - NASH_TEE_SPLICE_CHUNK, + HYDRASHELL_TEE_SPLICE_CHUNK, libc::SPLICE_F_MOVE, ) }; @@ -2254,7 +2254,7 @@ fn nash_tee_passthrough( // A kernel (or a seccomp policy) that will not splice these descriptors: finish the // link the portable way rather than dropping it. Some(libc::EINVAL | libc::ENOSYS | libc::EPERM) => { - nash_tee_copy(src, dst, total); + hydrashell_tee_copy(src, dst, total); return; } // Consumer gone (EPIPE) or a link that broke: its EOF is enough. @@ -2264,29 +2264,29 @@ fn nash_tee_passthrough( } #[cfg(not(target_os = "linux"))] -fn nash_tee_passthrough( +fn hydrashell_tee_passthrough( src: &mut std::io::PipeReader, dst: &mut std::io::PipeWriter, total: &mut u64, ) { - nash_tee_copy(src, dst, total); + hydrashell_tee_copy(src, dst, total); } -// nash: grow a tapped pipe's buffer, best-effort. The kernel caps unprivileged growth at +// hydrashell: grow a tapped pipe's buffer, best-effort. The kernel caps unprivileged growth at // `/proc/sys/fs/pipe-max-size` and simply refuses anything larger, which costs one failed syscall // per link and leaves the default in place. #[cfg(target_os = "linux")] -fn nash_grow_pipe(pipe: &F) { +fn hydrashell_grow_pipe(pipe: &F) { // SAFETY: `fcntl` with `F_SETPIPE_SZ` takes an int and only reads the descriptor's pipe size. - let _ = unsafe { libc::fcntl(pipe.as_raw_fd(), libc::F_SETPIPE_SZ, NASH_TEE_PIPE_SIZE) }; + let _ = unsafe { libc::fcntl(pipe.as_raw_fd(), libc::F_SETPIPE_SZ, HYDRASHELL_TEE_PIPE_SIZE) }; } #[cfg(not(target_os = "linux"))] -fn nash_grow_pipe(_pipe: &F) {} +fn hydrashell_grow_pipe(_pipe: &F) {} -// nash: record a regular-file redirect for post-run read-back (docs/NASH.md §5.2a). +// hydrashell: record a regular-file redirect for post-run read-back (docs/HYDRASHELL.md §5.2a). // The child still gets the real fd; this only notes what to read back afterward. -fn nash_record_file_redirect( +fn hydrashell_record_file_redirect( params: &mut ExecutionParameters, fd: ShellFd, kind: &ast::IoFileRedirectKind, @@ -2303,7 +2303,7 @@ fn nash_record_file_redirect( // Size at setup: append ranges start here; only regular files are read back. let size_before = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0); let op = if fd == 2 && op == ">" { "2>".to_string() } else { op.to_string() }; - params.nash_redirects.push(crate::gate::RedirectRecord { + params.hydrashell_redirects.push(crate::gate::RedirectRecord { op, fd: u32::try_from(fd).unwrap_or(0), path: Some(path.to_path_buf()), @@ -2313,14 +2313,14 @@ fn nash_record_file_redirect( }); } -// nash: record an inline (heredoc / here-string) redirect — body known at setup. -fn nash_record_inline_redirect( +// hydrashell: record an inline (heredoc / here-string) redirect — body known at setup. +fn hydrashell_record_inline_redirect( params: &mut ExecutionParameters, fd: ShellFd, op: &str, body: &str, ) { - params.nash_redirects.push(crate::gate::RedirectRecord { + params.hydrashell_redirects.push(crate::gate::RedirectRecord { op: op.to_string(), fd: u32::try_from(fd).unwrap_or(0), path: None, diff --git a/brush-core/src/lib.rs b/brush-core/src/lib.rs index 2708c82..c067ea7 100644 --- a/brush-core/src/lib.rs +++ b/brush-core/src/lib.rs @@ -14,7 +14,7 @@ pub mod expansion; mod extendedtests; pub mod extensions; pub mod functions; -// nash: observation/enforcement gate (docs/NASH.md §4.2). +// hydrashell: observation/enforcement gate (docs/HYDRASHELL.md §4.2). pub mod gate; pub mod history; pub mod int_utils; diff --git a/brush-core/src/processes.rs b/brush-core/src/processes.rs index 6409b9a..5900ece 100644 --- a/brush-core/src/processes.rs +++ b/brush-core/src/processes.rs @@ -17,7 +17,7 @@ pub struct ChildProcess { pid: Option, /// If available, the process group ID of the child. pgid: Option, - // nash: gate token issued by `Gate::on_exec` for this command, reported + // hydrashell: gate token issued by `Gate::on_exec` for this command, reported // back via `Gate::on_exit` when the process completes. pub(crate) gate_token: Option, } @@ -59,7 +59,7 @@ impl ChildProcess { tokio::select! { output = &mut self.exec_future => { let output = output?; - // nash: report process completion to the gate exactly once. + // hydrashell: report process completion to the gate exactly once. if let Some(token) = self.gate_token.take() { crate::gate::gate().on_exit( token, @@ -90,7 +90,7 @@ impl ChildProcess { let result: Option> = checkable_future .now_or_never() .map(|result| result.map_err(Into::into)); - // nash: completion can also be observed via poll (e.g. job checks). + // hydrashell: completion can also be observed via poll (e.g. job checks). if let Some(Ok(output)) = &result { if let Some(token) = self.gate_token.take() { crate::gate::gate().on_exit( @@ -105,7 +105,7 @@ impl ChildProcess { } } -// nash: numeric exit code for gate reporting (128+signal for signal deaths). +// hydrashell: numeric exit code for gate reporting (128+signal for signal deaths). fn exit_code_of(status: &std::process::ExitStatus) -> i32 { if let Some(code) = status.code() { return code; diff --git a/brush-core/src/prompt.rs b/brush-core/src/prompt.rs index db38879..8ccc2b7 100644 --- a/brush-core/src/prompt.rs +++ b/brush-core/src/prompt.rs @@ -87,7 +87,7 @@ fn format_prompt_piece( if users::is_root() { "#".to_owned() } else { - // nash: the interactive shell symbol is the atom sign (U+269B) rather than `$`. + // hydrashell: the interactive shell symbol is the atom sign (U+269B) rather than `$`. "\u{269B}".to_owned() } } diff --git a/brush-shell/src/args.rs b/brush-shell/src/args.rs index cd038f9..bf712e2 100644 --- a/brush-shell/src/args.rs +++ b/brush-shell/src/args.rs @@ -14,14 +14,14 @@ brush is distributed under the terms of the MIT license. If you encounter any is For more information, visit https://brush.sh."; -// nash: rebranded usage/version strings for the Nucleic agent shell fork. +// hydrashell: rebranded usage/version strings for the Nucleic agent shell fork. const USAGE: &str = color_print::cstr!( - "nash [OPTIONS]... [SCRIPT_PATH [SCRIPT_ARGS]...]" + "hydrashell [OPTIONS]... [SCRIPT_PATH [SCRIPT_ARGS]...]" ); const VERSION: &str = const_format::concatcp!( productinfo::PRODUCT_VERSION, - " (Nucleic agent shell, brush fork; ", + " (Hydrangea agent shell, brush fork; ", productinfo::PRODUCT_GIT_VERSION, ")" ); diff --git a/brush-shell/src/entry.rs b/brush-shell/src/entry.rs index bf672b1..00cfce8 100644 --- a/brush-shell/src/entry.rs +++ b/brush-shell/src/entry.rs @@ -268,11 +268,11 @@ pub fn run() { std::process::exit(i32::from(exit_code)); } -// nash: a hook invoked right before the process exits, so an embedding shell -// (nash) can flush its observation event buffer (docs/NASH.md §6.1). +// hydrashell: a hook invoked right before the process exits, so an embedding shell +// (hash) can flush its observation event buffer (docs/HYDRASHELL.md §6.1). static EXIT_HOOK: std::sync::OnceLock> = std::sync::OnceLock::new(); -/// nash: registers a hook run immediately before the shell process exits. +/// hydrashell: registers a hook run immediately before the shell process exits. /// May be called at most once; later calls are ignored. pub fn set_exit_hook(hook: Box) { let _ = EXIT_HOOK.set(hook); diff --git a/brush-shell/src/productinfo.rs b/brush-shell/src/productinfo.rs index 653daa4..4eb5c55 100644 --- a/brush-shell/src/productinfo.rs +++ b/brush-shell/src/productinfo.rs @@ -1,8 +1,8 @@ //! Information about this shell project. -// nash: rebranded from "brush" for the Nucleic agent shell fork (docs/NASH.md §2). +// hydrashell: rebranded from "brush" for the Nucleic agent shell fork (docs/HYDRASHELL.md §2). /// The formal name of this product. -pub const PRODUCT_NAME: &str = "nash"; +pub const PRODUCT_NAME: &str = "hydrashell"; const PRODUCT_HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE"); const PRODUCT_REPO: &str = env!("CARGO_PKG_REPOSITORY"); @@ -27,8 +27,8 @@ pub const PRODUCT_GIT_VERSION: &str = git_version::git_version!( ); pub(crate) fn get_product_display_str() -> String { - // nash: identify as the Nucleic agent shell fork of brush. + // hydrashell: identify as the Nucleic agent shell fork of brush. std::format!( - "{PRODUCT_NAME} (Nucleic agent shell, brush fork) {PRODUCT_VERSION} ({PRODUCT_GIT_VERSION}) - {PRODUCT_DISPLAY_URI}" + "{PRODUCT_NAME} (hash — Hydrangea agent shell, brush fork) {PRODUCT_VERSION} ({PRODUCT_GIT_VERSION}) - {PRODUCT_DISPLAY_URI}" ) } diff --git a/brush-shell/tests/cases/compat/ifs.yaml b/brush-shell/tests/cases/compat/ifs.yaml index 64f6fb9..4bcacac 100644 --- a/brush-shell/tests/cases/compat/ifs.yaml +++ b/brush-shell/tests/cases/compat/ifs.yaml @@ -219,7 +219,7 @@ cases: echo "After: [$IFS]" - name: "IFS does not affect for loop literal words" - # nash: fixed by ExpansionPiece::LiteralText (literal words are not field-split) + # hydrashell: fixed by ExpansionPiece::LiteralText (literal words are not field-split) stdin: | IFS=':' for word in a:b:c; do @@ -249,7 +249,7 @@ cases: echo "Quoted: $var" - name: "IFS with partially quoted expansion" - # nash: fixed by ExpansionPiece::LiteralText (literal words are not field-split) + # hydrashell: fixed by ExpansionPiece::LiteralText (literal words are not field-split) stdin: | IFS=':' var="a:b:c" @@ -309,7 +309,7 @@ cases: echo "[$1]" - name: "IFS with glob pattern literal" - # nash: fixed by ExpansionPiece::LiteralText (literal words are not field-split) + # hydrashell: fixed by ExpansionPiece::LiteralText (literal words are not field-split) stdin: | IFS=':' # Glob patterns as literals shouldn't split