Merge nucleic/sleek-thistle-egret-fyej into dev
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Reference
|
||||
|
||||
These documents serve as reference material for the `brush` project.
|
||||
|
||||
* [Configuration file](configuration.md)
|
||||
* [Experimental features](experimental.md)
|
||||
* [Integration testing](integration-testing.md)
|
||||
* [Minimum Supported Rust Version (MSRV) policy](msrv-policy.md)
|
||||
* [Compatibility](compatibility.md)
|
||||
@@ -0,0 +1,209 @@
|
||||
# `bash` Compatibility Reference
|
||||
|
||||
This document details `brush`'s compatibility with `bash`, including supported features, known limitations, and how to report issues.
|
||||
|
||||
## Overview
|
||||
|
||||
`brush` aims for high compatibility with `bash`. We validate this through **1700+ compatibility test cases** that compare behavior against `bash` as an oracle.
|
||||
|
||||
**Compatibility snapshot:** Production-ready for most use cases. Your `.bashrc`, aliases, functions, and completions should "just work."
|
||||
|
||||
## Fully Supported Features ✅
|
||||
|
||||
### Shell Syntax & Control Flow
|
||||
|
||||
- `if`/`then`/`elif`/`else`/`fi` conditionals
|
||||
- `for`, `while`, `until` loops
|
||||
- Arithmetic `for` loops: `for ((i=0; i<10; i++))`
|
||||
- `case`/`esac` pattern matching
|
||||
- `&&`, `||` conditional execution
|
||||
- Subshells `()` and command grouping `{}`
|
||||
- Pipelines and pipeline negation `!`
|
||||
- Coprocesses: `coproc { ... }` and `coproc NAME { ... }`
|
||||
|
||||
### Expansions
|
||||
|
||||
- Brace expansion: `{a,b,c}`, `{1..10}`, `{a..z}`
|
||||
- Parameter expansion: `${var:-default}`, `${var:+set}`, `${var#pattern}`, `${var%pattern}`, `${var//find/replace}`, etc.
|
||||
- Command substitution: `$(cmd)`, `` `cmd` ``
|
||||
- Arithmetic expansion: `$((expr))`
|
||||
- Process substitution: `<(cmd)`, `>(cmd)`
|
||||
- Tilde expansion: `~`, `~user`
|
||||
- Globbing: `*`, `?`, `[...]`
|
||||
- Extended globbing: `?(pat)`, `*(pat)`, `+(pat)`, `@(pat)`, `!(pat)`
|
||||
- `globstar`: `**` recursive matching
|
||||
|
||||
### Builtins (50+)
|
||||
|
||||
- **I/O:** `echo`, `printf`, `read`, `mapfile`/`readarray`
|
||||
- **Variables:** `declare`, `local`, `export`, `unset`, `readonly`, `typeset`
|
||||
- **Control:** `break`, `continue`, `return`, `exit`
|
||||
- **Navigation:** `cd`, `pushd`, `popd`, `dirs`, `pwd`
|
||||
- **Jobs:** `jobs`, `fg`, `bg`, `wait`, `kill`
|
||||
- **Completion:** `complete`, `compgen`, `compopt`
|
||||
- **History:** `history`, `fc`
|
||||
- **Testing:** `test`, `[`, `[[`
|
||||
- **Sourcing & introspection:** `.`, `source`, `eval`, `caller`
|
||||
- **Misc:** `alias`, `unalias`, `hash`, `type`, `command`, `builtin`, `enable`, `help`, `times`, `ulimit`, `umask`, `trap`, `shopt`, `set`, `shift`, `getopts`
|
||||
|
||||
### Arrays
|
||||
|
||||
- Indexed arrays: `arr=(a b c)`, `${arr[0]}`, `${arr[@]}`
|
||||
- Associative arrays: `declare -A map`
|
||||
- Array slicing: `${arr[@]:start:length}`
|
||||
- Array operations: `${#arr[@]}`, `${!arr[@]}`, `${!arr[*]}`
|
||||
|
||||
### Job Control
|
||||
|
||||
- Background execution: `cmd &`
|
||||
- Suspend/resume: Ctrl+Z, `fg`, `bg`
|
||||
- Job listing: `jobs`
|
||||
- Process groups and pipelines
|
||||
|
||||
### Dynamic Variables
|
||||
|
||||
- `RANDOM`, `SRANDOM`
|
||||
- `LINENO`, `FUNCNAME`, `BASH_SOURCE`
|
||||
- `EPOCHSECONDS`, `EPOCHREALTIME`
|
||||
- `SECONDS`
|
||||
- `PWD`, `OLDPWD`
|
||||
- `BASH_VERSINFO`, `BASH_VERSION`
|
||||
|
||||
### Programmable Completion
|
||||
|
||||
- Compatible with [`bash-completion`](https://github.com/scop/bash-completion)
|
||||
- Git, Docker, systemctl, etc. completions work out of the box
|
||||
- `complete`, `compgen`, `compopt` builtins
|
||||
|
||||
### Redirection
|
||||
|
||||
- Standard: `>`, `>>`, `<`, `2>&1`
|
||||
- Here documents: `<<EOF`, `<<-EOF` (tab-stripped), `<<<` (here strings)
|
||||
- File descriptor manipulation: `>&n`, `<&n`, `n>&m`
|
||||
- Process substitution redirects: `>(cmd)`, `<(cmd)`
|
||||
- Clobber control: `>|`, `set -o noclobber`
|
||||
|
||||
## Partially Supported Features 🔷
|
||||
|
||||
### Traps
|
||||
|
||||
| Status | Feature |
|
||||
|--------|----------|
|
||||
| ✅ | `DEBUG` trap |
|
||||
| ✅ | `ERR` trap |
|
||||
| ✅ | `EXIT` trap |
|
||||
| 🔷 | Signal traps (`SIGINT`, `SIGTERM`, etc.) — in progress |
|
||||
|
||||
### Key Bindings (`bind`)
|
||||
|
||||
| Status | Feature |
|
||||
|--------|---------|
|
||||
| ✅ | Basic `bind` support |
|
||||
| ✅ | `bind -x` for custom key-bound commands |
|
||||
| 🔷 | Advanced bind features — in progress |
|
||||
|
||||
### Shell Options
|
||||
|
||||
| Status | Feature |
|
||||
|--------|---------|
|
||||
| ✅ | Common options: `errexit`, `pipefail`, `extglob`, `globstar`, `noclobber`, `nounset`, `failglob` |
|
||||
| 🔷 | Less common options — in progress |
|
||||
|
||||
## Not Yet Supported Features 🚧
|
||||
|
||||
These features are on our roadmap but not yet implemented:
|
||||
|
||||
### `select` Statement
|
||||
|
||||
The `select` builtin for creating menu-driven scripts is not yet implemented.
|
||||
|
||||
```bash
|
||||
# Not yet supported
|
||||
select opt in "Option A" "Option B" "Quit"; do
|
||||
case $opt in
|
||||
"Option A") echo "A";;
|
||||
"Option B") echo "B";;
|
||||
"Quit") break;;
|
||||
esac
|
||||
done
|
||||
```
|
||||
|
||||
### `wait -n`
|
||||
|
||||
The `wait -n` option to wait for the next background job to complete is not implemented.
|
||||
|
||||
```bash
|
||||
# Not yet supported
|
||||
job1 &
|
||||
job2 &
|
||||
wait -n # Wait for whichever finishes first
|
||||
```
|
||||
|
||||
### `BASH_COMMAND` Variable
|
||||
|
||||
The special variable `BASH_COMMAND` that contains the currently executing command is currently only available in trap contexts.
|
||||
|
||||
### `disown` and `logout`
|
||||
|
||||
These job control builtins are not yet implemented.
|
||||
|
||||
## Known Edge Cases
|
||||
|
||||
These areas have known differences from `bash` in edge cases. Most users won't encounter these, but they're documented for completeness.
|
||||
|
||||
### IFS (Input Field Separator)
|
||||
|
||||
There are ~10 known edge cases where IFS word splitting behavior differs from `bash`, particularly around:
|
||||
|
||||
- Non-whitespace IFS characters and empty field creation
|
||||
- Mixed whitespace and non-whitespace IFS
|
||||
- Leading/trailing delimiter handling
|
||||
|
||||
### `printf` Format Specifiers
|
||||
|
||||
Some advanced `printf` format specifiers behave differently (~8 known cases), particularly features not supported by the underlying `uucore` library.
|
||||
|
||||
### Arithmetic Expressions
|
||||
|
||||
- Division by zero handling may differ in `errexit` mode
|
||||
- `$(( exit N ))` syntax edge case
|
||||
|
||||
### Aliases
|
||||
|
||||
Some complex alias expansion scenarios differ from `bash` (see GitHub issues #57, #286).
|
||||
|
||||
## Test Suite Statistics
|
||||
|
||||
- **Total test cases:** 1700+
|
||||
- **Known failures:** ~125
|
||||
- **Most failures are edge cases** in IFS handling and printf
|
||||
|
||||
The test suite runs on every PR and compares behavior against `bash` as an oracle.
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
`brush` targets compatibility with **`bash` 5.3+**. Behavior may differ from older `bash` versions (3.x, 4.x) in some areas.
|
||||
|
||||
## Reporting Compatibility Issues
|
||||
|
||||
Found a script that works in `bash` but not in `brush`?
|
||||
|
||||
1. **Check existing issues:** [GitHub Issues](https://github.com/reubeno/brush/issues)
|
||||
2. **Create a minimal reproducer:** Reduce to the smallest failing script
|
||||
3. **File an issue** with:
|
||||
- The script or command that fails
|
||||
- Expected behavior (what `bash` does)
|
||||
- Actual behavior (what `brush` does)
|
||||
- Your platform (Linux/macOS/etc.)
|
||||
|
||||
## Tracking Progress
|
||||
|
||||
- **GitHub Issues:** Track specific compatibility work
|
||||
- **Test Suite:** 1700+ tests run on every PR
|
||||
- **This Document:** Updated as features are implemented
|
||||
|
||||
## Related Resources
|
||||
|
||||
- [`bash` Reference Manual](https://www.gnu.org/software/bash/manual/)
|
||||
- [POSIX Shell Specification](https://pubs.opengroup.org/onlinepubs/9699919799/)
|
||||
- [`brush` Test Cases](https://github.com/reubeno/brush/tree/main/brush-shell/tests/cases)
|
||||
@@ -0,0 +1,118 @@
|
||||
# Configuration File
|
||||
|
||||
brush supports an optional TOML configuration file that allows you to customize shell behavior without command-line arguments.
|
||||
|
||||
## File Location
|
||||
|
||||
`brush` looks for the configuration file at:
|
||||
|
||||
- **Linux/macOS**: `${XDG_CONFIG_HOME}/brush/config.toml`*
|
||||
- **Windows**: `%APPDATA%\brush\config.toml`
|
||||
|
||||
> [!NOTE]
|
||||
> On Linux/macOS falls back to `~/.config/brush/config.toml` if `XDG_CONFIG_HOME` is undefined.
|
||||
|
||||
You can override this location with the `--config` flag:
|
||||
|
||||
```bash
|
||||
brush --config /path/to/custom/config.toml
|
||||
```
|
||||
|
||||
To disable configuration file loading entirely, use:
|
||||
|
||||
```bash
|
||||
brush --no-config
|
||||
```
|
||||
|
||||
## Configuration Priority
|
||||
|
||||
Settings are applied in the following order (later values override earlier ones):
|
||||
|
||||
1. **Defaults** - Built-in default values
|
||||
2. **Configuration file** - Values from `config.toml`
|
||||
3. **Command-line arguments** - Flags passed to brush
|
||||
|
||||
## File Format
|
||||
|
||||
The configuration file uses [TOML](https://toml.io/) format. All settings are optional; brush uses sensible defaults for any unspecified values.
|
||||
|
||||
### Example Configuration
|
||||
|
||||
```toml
|
||||
[ui]
|
||||
syntax-highlighting = true
|
||||
|
||||
[experimental]
|
||||
zsh-hooks = true
|
||||
terminal-shell-integration = true
|
||||
```
|
||||
|
||||
## Available Settings
|
||||
|
||||
### `[ui]` Section
|
||||
|
||||
User interface settings.
|
||||
|
||||
| Setting | Type | Default | CLI flag | Description |
|
||||
|-----------------------|---------|---------|-------------------------|----------------------------------------------|
|
||||
| `syntax-highlighting` | boolean | see below | `--enable-highlighting` | Enable syntax highlighting in the input line |
|
||||
|
||||
> The default value of `syntax-highlighting` depends on how `brush-shell`
|
||||
> was built: `true` when built with the `experimental` Cargo feature,
|
||||
> `false` otherwise. CLI flags take precedence over the configuration
|
||||
> file.
|
||||
|
||||
### `[experimental]` Section
|
||||
|
||||
Experimental features that may change or be removed in future versions.
|
||||
Each setting has an equivalent command-line flag; CLI flags take
|
||||
precedence over the configuration file. See the
|
||||
[experimental features reference](experimental.md) for details on each
|
||||
feature.
|
||||
|
||||
| Setting | Type | Default | CLI flag | Description |
|
||||
|------------------------------|---------|---------|----------------------------------|---------------------------------------|
|
||||
| `zsh-hooks` | boolean | `false` | `--enable-zsh-hooks` | Enable zsh-style preexec/precmd hooks |
|
||||
| `terminal-shell-integration` | boolean | `false` | `--enable-terminal-integration` | Enable terminal shell integration |
|
||||
|
||||
## JSON Schema
|
||||
|
||||
A JSON Schema for the configuration file is available at [`schemas/config.schema.json`](../../schemas/config.schema.json). This can be used with editors that support schema-based validation and autocompletion for TOML files.
|
||||
|
||||
### Using the Schema with VS Code
|
||||
|
||||
To enable schema validation in VS Code with the [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) extension, add this to your `config.toml`:
|
||||
|
||||
```toml
|
||||
#:schema https://raw.githubusercontent.com/reubeno/brush/main/schemas/config.schema.json
|
||||
|
||||
[ui]
|
||||
syntax-highlighting = true
|
||||
```
|
||||
|
||||
The `#:schema` directive tells the editor where to find the schema for validation and autocompletion.
|
||||
|
||||
### Using the Schema with Other Editors
|
||||
|
||||
Many editors support JSON Schema for TOML files. Consult your editor's documentation for how to associate a schema with a file. You can reference the schema via:
|
||||
|
||||
- **URL**: `https://raw.githubusercontent.com/reubeno/brush/main/schemas/config.schema.json`
|
||||
- **Local path**: Point to `schemas/config.schema.json` in your brush source checkout
|
||||
|
||||
## Sample Configuration
|
||||
|
||||
A sample configuration file is available at [`samples/config.toml`](../../samples/config.toml) in the brush repository. You can copy this file to get started:
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
mkdir -p ~/.config/brush
|
||||
cp samples/config.toml ~/.config/brush/config.toml
|
||||
```
|
||||
|
||||
## Forward Compatibility
|
||||
|
||||
brush ignores unknown settings in the configuration file. This allows configuration files to be shared across different versions of brush without causing errors.
|
||||
|
||||
## Error Handling
|
||||
|
||||
If the configuration file cannot be read or parsed, brush logs an error message and continues with default settings. The shell will still start normally.
|
||||
@@ -0,0 +1,149 @@
|
||||
# Experimental Features
|
||||
|
||||
`brush` ships several features that are intentionally marked as
|
||||
**experimental**. They are usable today, but their interface or behavior
|
||||
may evolve based on feedback before being stabilized. This page is the
|
||||
canonical index of what's currently experimental and how to opt in.
|
||||
|
||||
Experimental features fall into two categories:
|
||||
|
||||
1. **Build-time experiments** — additional functionality gated behind
|
||||
Cargo feature flags. To get them, you build `brush-shell` with the
|
||||
relevant feature(s) enabled.
|
||||
2. **Run-time experiments** — features that ship in standard builds but
|
||||
are off by default and enabled through configuration.
|
||||
|
||||
> Names, defaults, and semantics of experimental features may change
|
||||
> between releases.
|
||||
|
||||
---
|
||||
|
||||
## Build-time experiments (Cargo features)
|
||||
|
||||
These are flags on the `brush-shell` crate. You can enable them
|
||||
individually, or pull in the whole set with the umbrella `experimental`
|
||||
feature.
|
||||
|
||||
```bash
|
||||
# Enable everything experimental
|
||||
cargo install --locked brush-shell --features experimental
|
||||
|
||||
# Or pick and choose
|
||||
cargo install --locked brush-shell --features experimental-bundled-coreutils
|
||||
```
|
||||
|
||||
### `experimental-bundled-coreutils`
|
||||
|
||||
Bundles a configurable subset of [`uutils/coreutils`](https://github.com/uutils/coreutils)
|
||||
implementations directly into `brush-shell` as builtins. Useful when:
|
||||
|
||||
- shipping `brush` into containers, embedded systems, or other
|
||||
environments where a standalone coreutils package is inconvenient or
|
||||
unavailable,
|
||||
- distributing a single self-contained `brush` binary that doesn't
|
||||
rely on host utilities being present.
|
||||
|
||||
When enabled (via the umbrella `experimental` feature, or directly), the
|
||||
full set of supported utilities is bundled. When building the
|
||||
`brush-coreutils-builtins` crate directly, individual utilities can be
|
||||
selected via `coreutils.<name>` features (e.g., `coreutils.cat`,
|
||||
`coreutils.ls`); the `coreutils.all` feature enables all of them.
|
||||
|
||||
Bundled utilities run in-process and take precedence over external
|
||||
executables of the same name on `PATH` when invoked unqualified. As with
|
||||
any builtin, you can bypass the in-process implementation with
|
||||
`command <name>` or by giving an explicit path.
|
||||
|
||||
### `experimental-builtins`
|
||||
|
||||
Pulls in the [`brush-experimental-builtins`](../../brush-experimental-builtins)
|
||||
crate, which provides additional builtins that are too new or too
|
||||
narrow-purpose to ship in the default builtin set. Currently this
|
||||
includes:
|
||||
|
||||
- **`save`** — serializes the current shell state to JSON on stdout.
|
||||
Primarily intended for debugging and tooling. ⚠️ The serialized state
|
||||
may include sensitive information (variable values, command history,
|
||||
environment).
|
||||
|
||||
### `experimental-load`
|
||||
|
||||
Enables `serde`-based serialization support in `brush-core`, and adds a
|
||||
`--load <FILE>` command-line flag that restores shell state from a JSON
|
||||
file previously produced by the experimental [`save`](#experimental-builtins)
|
||||
builtin (or by other tooling that emits the same format). State loaded
|
||||
this way overrides non-UI command-line options. Useful for tooling
|
||||
built on top of `brush-core` that wants to snapshot or transfer shell
|
||||
state.
|
||||
|
||||
### `experimental-parser`
|
||||
|
||||
Switches on the in-development [`winnow`](https://crates.io/crates/winnow)-based
|
||||
parser scaffolding in `brush-parser`, and adds an `--experimental-parser`
|
||||
command-line flag that selects it at runtime. This parser is not yet the
|
||||
production parser; the existing PEG parser remains the default. Enable
|
||||
this only if you're contributing to or experimenting with the parser
|
||||
work.
|
||||
|
||||
---
|
||||
|
||||
## Run-time experiments (configuration)
|
||||
|
||||
These features ship in standard builds but are off by default. Each one
|
||||
can be enabled either through the optional [TOML configuration
|
||||
file](configuration.md) (under the `[experimental]` section, persistent
|
||||
across sessions) or through a command-line flag at startup (one-shot).
|
||||
|
||||
When both are specified, command-line flags take precedence over the
|
||||
configuration file.
|
||||
|
||||
```toml
|
||||
[experimental]
|
||||
zsh-hooks = true
|
||||
terminal-shell-integration = true
|
||||
```
|
||||
|
||||
| Feature | TOML setting (`[experimental]`) | Command-line flag |
|
||||
|---------|---------------------------------|-------------------|
|
||||
| zsh-style hooks | `zsh-hooks = true` | `--enable-zsh-hooks` |
|
||||
| Terminal shell integration | `terminal-shell-integration = true` | `--enable-terminal-integration` |
|
||||
|
||||
### `zsh-hooks`
|
||||
|
||||
Enables zsh-style `preexec` and `precmd` hook functions. When set:
|
||||
|
||||
- A function named `preexec` (if defined) is invoked before each
|
||||
interactively-entered command runs, with the command line as `$1`.
|
||||
- A function named `precmd` (if defined) is invoked just before each
|
||||
prompt is displayed.
|
||||
|
||||
This is convenient for prompt frameworks, command timing, and
|
||||
integrations that expect zsh-style hook conventions. Equivalent
|
||||
behavior in stock bash typically requires `DEBUG`/`PROMPT_COMMAND`
|
||||
plumbing.
|
||||
|
||||
Enable persistently with `zsh-hooks = true` under `[experimental]` in
|
||||
`config.toml`, or per-invocation with `brush --enable-zsh-hooks`.
|
||||
|
||||
### `terminal-shell-integration`
|
||||
|
||||
Emits standard terminal shell-integration escape sequences (semantic
|
||||
prompt and command boundary marking) that modern terminal emulators —
|
||||
including VS Code, iTerm2, WezTerm, and others — use to enable features
|
||||
like command navigation, exit-status display, and selective output
|
||||
copying. This is off by default to avoid emitting escape sequences in
|
||||
terminals that don't recognize them.
|
||||
|
||||
Enable persistently with `terminal-shell-integration = true` under
|
||||
`[experimental]` in `config.toml`, or per-invocation with
|
||||
`brush --enable-terminal-integration`.
|
||||
|
||||
---
|
||||
|
||||
## Reporting feedback
|
||||
|
||||
Experimental features are the place where your feedback is most valuable
|
||||
— they exist precisely because we want to iterate on them before
|
||||
stabilizing. If you try one and find a rough edge, missing capability,
|
||||
or behavior that surprises you, please [file an
|
||||
issue](https://github.com/reubeno/brush/issues).
|
||||
@@ -0,0 +1,17 @@
|
||||
# Integration testing
|
||||
|
||||
Our approach to integration testing relies heavily on using test oracles to provide the "correct" answers/expectations for test cases. In practice, we use existing alternate shell implementations as oracles.
|
||||
|
||||
Test cases are defined in YAML files. The test cases defined in a given file comprise a test case set. Running the integration tests for this project executes test case sets in parallel.
|
||||
|
||||
```yaml
|
||||
name: "Example tests"
|
||||
cases:
|
||||
- name: "Basic usage"
|
||||
stdin: |
|
||||
echo hi
|
||||
```
|
||||
|
||||
This defines a new test case set with the name "Example tests". It contains one defined test case called "Basic usage". This test case will launch the shell without any additional custom arguments (beyond a few standard ones to disable processing default profiles and rc files), write "echo hi" (with a trailing newline) to stdin of the shell, and then close that stream. The test harness will capture the shell's stdout, stderr, and exit code. After repeating these steps with the test oracle, each of these 3 data are compared. An error is flagged if any of the 3 differ.
|
||||
|
||||
Test cases are run with the working directory initialized to a temporary directory. The contents of the temporary directory are inspected after the shell-under-test has exited, and compared against their counterparts in the oracle's run. This enables easy checking of files created, deleted, or mutated as side effects of running the test case.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Minimum Supported Rust Version (MSRV) Policy
|
||||
|
||||
## Overview
|
||||
|
||||
The `brush` project maintains a conservative MSRV policy to balance two key concerns:
|
||||
|
||||
1. **Binary distribution**: Users building `brush` from source should not need a bleeding-edge compiler
|
||||
2. **Library usage**: Downstream projects depending on `brush` crates should not face aggressive MSRV increases
|
||||
|
||||
## Policy
|
||||
|
||||
### When We Update MSRV
|
||||
|
||||
We **do not** update MSRV proactively. Updates only occur when:
|
||||
|
||||
- A meaningful set of language features or capabilities becomes available that provides clear value to the project
|
||||
- The return-on-investment justifies the potential impact on users and downstream dependencies
|
||||
|
||||
### MSRV Age Requirements
|
||||
|
||||
When we do update MSRV, we move to a Rust version that is **at least 4-6 months old** from the time of the update. This ensures:
|
||||
|
||||
- Sufficient time for the Rust version to stabilize
|
||||
- Wide availability in package managers and development environments
|
||||
- Reduced friction for users building from source
|
||||
|
||||
### Communication
|
||||
|
||||
MSRV changes are always:
|
||||
|
||||
- Explicitly documented in release notes
|
||||
- Considered a notable change requiring user awareness
|
||||
- Announced with clear justification for the update
|
||||
|
||||
## Rationale
|
||||
|
||||
This conservative approach recognizes that `brush` serves dual purposes: as a standalone binary tool and as a library for integration into other projects. Both use cases benefit from stability and predictability in compiler requirements.
|
||||
Reference in New Issue
Block a user