# M4b task 01: move the text escaping into `proto` **Branch:** `m4b` (run `git switch m4b`; `git status --short` must be empty, otherwise stop) **Commit subject:** `proto: escape, moved from bxctl so gatewayd shows the same thing` ## Goal `bxctl` escapes text it did not write (model output, tool arguments, server errors) before it reaches the terminal: control characters, zero-width and bidirectional marks, line separators and the BOM become `\uXXXX`. M4b posts tool arguments in Mattermost, and they must be escaped by the same rules, so the code moves to `proto`, where both can use it. **Nothing changes in what it does.** `bxctl::escape` stays, as a re-export, so no `bxctl` code or test changes. ## Files - Copy: `crates/proto/src/escape.rs` (complete), `crates/bxctl/src/escape.rs` (complete, replaces the old one), `crates/proto/tests/escape.rs` - Modify: `crates/proto/src/lib.rs` (`pub mod escape;`, after `pub mod class;`), `docs/implementer-log.md` `proto/src/escape.rs` is `bxctl`'s old file with a new module comment. `bxctl/src/escape.rs` becomes one line: `pub use proto::escape::{escape_json_text, escape_model_text};`. The new test is `bxctl`'s escape test, pointed at `proto::escape`. ## Steps - [ ] **1. Copy.** `git switch m4b`, then `cp docs/plans/M4b/files/crates/proto/src/escape.rs crates/proto/src/ && cp docs/plans/M4b/files/crates/bxctl/src/escape.rs crates/bxctl/src/ && cp docs/plans/M4b/files/crates/proto/tests/escape.rs crates/proto/tests/`. Add `pub mod escape;` to `crates/proto/src/lib.rs`. - [ ] **2. See it pass.** `cargo test -p proto --test escape` (8 passed) and `cargo test -p bxctl` (every suite passes, unchanged). - [ ] **3. Run the gate.** `cargo fmt --all`, then `make gate`. Expected last line: `gate: ok`. - [ ] **4. Log and commit.** `git add crates/proto crates/bxctl docs/implementer-log.md Cargo.lock && git commit` ## Done when - Both crates' tests pass; `make gate` prints `gate: ok`. ## Stop and report if - Any `bxctl` test changes result: the move must not change behaviour.