headroom/crates
Zhenjia ZHOU 844d9caaa1
feat(text-crusher): fold full-width ASCII to half-width in CJK token keys (#2259)
## Description

Real CJK content routinely mixes full-width and half-width forms (`API`
vs `API`, `0` vs `0`, the ideographic space ` ` vs a normal space).
After #1504's ICU tokenization, these width variants produced
*different* token keys, so `API` and `API` didn't dedup or match as the
same term on the CJK relevance and near-duplicate paths.

This folds full-width ASCII (U+FF01–U+FF5E, via the U+FEE0 offset) and
the ideographic space (U+3000 → space) to their half-width forms **when
building the internal token key** inside `tokens_icu`. Only the token
key is normalized — the kept output stays byte-verbatim, so
TextCrusher's extractive / byte-faithful contract is preserved.
CJK-gated (`tokens_icu` is the CJK path); the ASCII path is untouched.

## Type of Change

- [x] Bug fix / enhancement (non-breaking)

## Changes Made

- `crates/headroom-core/src/transforms/text_crusher/crusher.rs`: a
`width_fold(c)` helper applied when building token keys in `tokens_icu`.
- Rust unit tests: full-width ASCII folds to half-width in token keys;
CJK segments split on full-width terminators.

## Testing

- [x] Unit tests pass (`cargo test`)
- [x] Linting passes (`cargo clippy` / `cargo fmt`)
- [x] New tests added

### Test Output

```text
$ cargo test -p headroom-core --lib text_crusher
test result: ok. 13 passed; 0 failed
$ cargo clippy / fmt   # clean
```

## Real Behavior Proof

- Environment: macOS (Darwin), Rust via cargo, branch
`feat/text-crusher-fullwidth-fold` off `main` (rebased after #1504
merged).
- Exact command / steps: `cargo test -p headroom-core --lib
text_crusher`.
- Observed result: `fullwidth_ascii_folds_to_halfwidth` confirms `API`
and `API` now produce the same token key (so they dedup/relevance-match
as one term); `cjk_splits_on_full_width_terminators` confirms full-width
`!`/`?` terminate segments. All 13 text_crusher tests pass; the kept
output is byte-verbatim (only the internal key is folded).
- Not tested: no Python side — TextCrusher is Rust-only, and output
stays byte-verbatim, so no parity fixtures change.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation — N/A
- [x] My changes generate no new warnings
- [x] I have added tests that prove my change is effective
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md — happy to add an entry if
preferred.

## Additional Notes

- Follow-up to #1504 (CJK-aware TextCrusher); it only touches the
CJK-gated `tokens_icu` path, so English tokenization is unchanged.
2026-07-16 13:51:42 -07:00
..
headroom-core feat(text-crusher): fold full-width ASCII to half-width in CJK token keys (#2259) 2026-07-16 13:51:42 -07:00
headroom-parity feat: compression extraction — Rust knob exposure, CCR hardening, traffic audits (#818) 2026-06-16 20:21:13 -07:00
headroom-proxy fix(proxy): satisfy rustfmt import ordering (#2158) 2026-07-14 11:53:06 -04:00
headroom-py feat(transforms): language-aware stack-trace collapse for Go/Rust/.NET/Java/Node (#1791) 2026-07-15 19:58:30 +00:00
headroom-simulators feat(simulators): add provider simulator service (#2014) 2026-07-11 09:41:49 -07:00