nucleic-brush

A fork of brush — the Bo(u)rn(e) RUsty SHell by reuben olinsky — carrying the patches Nucleic needs to build hash, its agent shell.

Pinned at upstream tag brush-shell-v0.4.0 (commit 96a26d0).

Looking for the shell itself? Go upstream. This repository is not a distribution of brush and publishes no binaries or crates — it is a vendored fork whose only consumer is Nucleic's shell/ workspace. Everything here that isn't marked as a fork change is upstream's work, under upstream's MIT license.

What the fork adds

Nucleic runs agent-authored shell commands and needs to see what they do — the commands, their exits, and the data crossing pipes and redirections — from inside the shell rather than by wrapping it. That is what these patches provide. Everything else is upstream brush, unmodified.

Every divergence carries a // hydrashell: comment, so the complete diff against upstream is one grep away:

$ grep -rn "hydrashell:" --include="*.rs" --include="*.yaml" .

1. The observation gate

A process-global, verdict-shaped hook at the shell's command choke point (brush-core/src/gate.rs). The default implementation allows everything and observes nothing, so an unconfigured shell behaves exactly like upstream brush — including the fast paths: pipe teeing is only wired up when a real gate is installed.

Surface What it reports
Gate::on_exec / on_exit every simple command (builtin, function, external): argv, cwd, redirections, pipeline slot; then its exit code (128+signal for signal deaths)
Gate::on_pipe the bytes crossing each a | b link once it drains, with both stages rendered from their pre-expansion AST text
Gate::on_cmdsub the trimmed output of $(…) / backtick substitutions

on_exec returns a Verdict, so the same seam supports enforcement (Deny) later without further changes to this crate. Today Nucleic only observes.

The plumbing behind those hooks:

File Change
brush-core/src/gate.rs (new), lib.rs the Gate trait, event types, and process-global install/allow-all default
brush-core/src/commands.rs SimpleCommand::execute split into a gate wrapper + execute_inner; on_exec runs before dispatch and correlates completion across all three spawn-result variants
brush-core/src/processes.rs ChildProcess.gate_token and on_exit reporting from both wait() and poll(), exactly once
brush-core/src/interp.rs redirection recording (>, >>, <, 2>, &>, <>, heredocs, here-strings) for post-run read-back; a tee interposed on each pipe link when observation is active — a pooled copier thread mirrors a bounded prefix while preserving backpressure and SIGPIPE, with a splice passthrough after the cap
brush-core/src/expansion.rs command-substitution arm reports its output to the gate
brush-shell/src/entry.rs set_exit_hook seam so an embedder can flush buffered events before process::exit

2. Upstream-candidate fixes

Bugs found by running real-world scripts through brush. These aren't Nucleic-specific and belong upstream:

File Fix
brush-core/src/expansion.rs literal unquoted word text is no longer field-split (bash splits only the results of expansions) while keeping its glob characters active. Parameter-expansion substitutions convert back to splittable at the expansion boundary; ExpanderOptions.field_split_literal_text lets data-string callers opt in. Repairs 3 previously known_failure IFS tests.
brush-core/src/completion.rs compgen -W opts into field_split_literal_text — the -W string is data, not source text
brush-builtins/src/dot.rs . / source searches PATH for slashless operands when sourcepath is set, as bash and POSIX require. Fixes git-subtree, which sources git-sh-setup this way.

3. Branding

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 → 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 #

NUCLEIC_FORK.md tracks the same list at file granularity, with the current verification status.

How it's consumed

Nucleic's shell/ workspace depends on brush-shell and brush-parser by path. The 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.

Building and testing

Standard upstream workflow — a recent Rust toolchain (see rust-version in Cargo.toml), then:

$ cargo build --release
$ cargo test --workspace
$ cargo test -p brush-shell --test brush-compat-tests   # the bash-oracle compat suite

The compat suite is the fork's regression gate: it must stay level with a pristine build of the pinned upstream tag. As last recorded in NUCLEIC_FORK.md, it does — the only failures are environmental ones a baseline run reproduces, plus the 3 IFS tests the expansion fix repairs.

Staying in sync with upstream

  1. Diff this tree against the pinned upstream tag.
  2. Re-vendor the new tag.
  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.

Fixes that aren't Nucleic-specific should go to upstream rather than accumulate here — a smaller diff is a cheaper rebase.

Upstream documentation

Retained as vendored, and still accurate for everything the fork doesn't touch:

Credits and license

brush is written by reuben olinsky and its contributors: https://github.com/reubeno/brush. It is an excellent piece of work, and the reason Nucleic could get an observable shell by patching rather than by writing one.

This fork is distributed under the same MIT license as upstream; the copyright notice is unchanged. Upstream's community guidelines and contribution guidelines are vendored here and describe upstream's process — follow them there, not in this repository.

S
Description
No description provided
Readme MIT
985 KiB
Languages
Rust 97.3%
Python 2.1%
Shell 0.4%