# 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 **hash**, its agent shell. Pinned at upstream tag **`brush-shell-v0.4.0`** (commit `96a26d0`). > **Looking for the shell itself?** Go [upstream](https://github.com/reubeno/brush). > 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](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: ```console $ 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`](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`](Cargo.toml)), then: ```console $ 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`](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`](NUCLEIC_FORK.md). Fixes that aren't Nucleic-specific should go to [upstream](https://github.com/reubeno/brush/issues) 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: * [Compatibility reference](docs/reference/compatibility.md) — what does and doesn't work versus bash * [Configuration](docs/reference/configuration.md) * [Building from source](docs/how-to/build.md) · [Running tests](docs/how-to/run-tests.md) * [Full docs index](docs/README.md) ## Credits and license brush is written by **reuben olinsky** and its contributors: . 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](LICENSE) as upstream; the copyright notice is unchanged. Upstream's [community guidelines](CODE_OF_CONDUCT.md) and [contribution guidelines](CONTRIBUTING.md) are vendored here and describe *upstream's* process — follow them there, not in this repository.