headroom/tests/test_transforms
Parideboy 6d5516dcb8
feat(code): add PHP support to CodeAwareCompressor (#2423)
## Description

Adds PHP to `CodeAwareCompressor`, fixing #201. PHP was already
*detected* as code (Magika labels in `headroom/compression/detector.py`
include `php`, and the Rust `magika_detector.rs` lists it too) but there
was no PHP `LangConfig`, so PHP content silently passed through
uncompressed. This wires PHP through the tree-sitter compression path
following the C# pattern (the most recently added, fully functional
language — deliberately not the quarantined Perl path).

A secondary detection bug is fixed along the way: PHP's `$variables`
match Perl's prefilter regex, and the existing Perl-dominance guard in
`detect_language` returned `UNKNOWN` for PHP files. An explicit `<?php`
open tag — which no Perl source contains — now drops Perl from the
candidate set before that guard runs.

## Type of Change

- [ ] Bug fix
- [x] New feature
- [ ] Documentation update
- [ ] Refactor
- [ ] Other

## Changes Made

- `headroom/transforms/code_compressor.py`: `CodeLanguage.PHP` +
`phtml`/`php5`/`php7`/`php8` aliases; PHP `LangConfig` built from the
actual tree-sitter-php grammar (node names verified by parsing samples):
`namespace_use_declaration` imports,
`function_definition`/`method_declaration` functions,
`class_declaration`/`interface_declaration`/`trait_declaration` classes,
`enum_declaration` types, `declaration_list` class bodies,
`compound_statement` function bodies. `namespace_definition` maps to
`package_node` so statement-scoped `namespace App;` hoists ahead of the
`use` imports (required PHP ordering); the rare block-scoped `namespace
A { }` form takes the same path and is preserved verbatim — valid
output, just no compression inside the block. PHP prefilter regexes
added; supported-languages error message updated; `<?php`-tag Perl
disambiguation in `detect_language`.
- `headroom/transforms/content_detector.py`: `php` entry in
`_CODE_PATTERNS` so raw PHP classifies as `SOURCE_CODE` and reaches the
code-aware route.
- `tests/test_transforms/test_code_compressor.py`: new `TestPhpSupport`
mirroring `TestCSharpSupport` — signatures preserved / bodies elided,
`<?php` → `namespace` → `use` → declarations ordering, auto-detection
despite the Perl sigil overlap, alias coercion, malformed passthrough.
- `tests/test_code_compressor_language_alias.py`: `php` in the canonical
list, `phtml` in the alias table.
- `docs/content/docs/code-compression.mdx`: PHP added to the Tier 2
supported-languages row.

No new dependency: `tree-sitter-language-pack` (the existing `[code]`
extra) already ships the PHP grammar. No Rust changes needed.

## Testing

- [x] New unit tests added and passing
- [x] Full affected test suites pass locally

**Test Output**

```
$ python -m pytest tests/test_transforms/test_code_compressor.py tests/test_code_compressor_language_alias.py -q
============================= 120 passed in 7.81s =============================

$ python -m pytest tests/test_transforms/ -q
3 failed, 443 passed   # the 3 failures (kompress ONNX thread caps, kompress size gate,
                       # text_crusher unicode parity) reproduce identically on a clean
                       # upstream/main checkout in this environment — pre-existing local
                       # ONNX runtime quirks, unrelated to this change

$ ruff check . (0.15.17, CI-pinned) → All checks passed!  |  ruff format --check → clean
$ mypy headroom/transforms/code_compressor.py headroom/transforms/content_detector.py --ignore-missing-imports
Success: no issues found in 2 source files
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.13, tree-sitter +
tree-sitter-language-pack (<1.0) installed, branch
`feat/201-php-code-compression` off `upstream/main`.
- Exact command / steps: parsed PHP samples (namespaced class w/
methods, block-scoped namespace, mixed HTML+PHP) with
`tree_sitter_language_pack.get_parser('php')` to verify every node name
used in the config; then ran `CodeAwareCompressor().compress(php_code,
language="php")` and `compress(php_code)` (auto-detection) on a 48-line
realistic service class.
- Observed result: explicit and auto-detected paths both return
`language=CodeLanguage.PHP`, `compression_ratio=0.64`,
`syntax_valid=True`; method bodies elided to `// [N lines omitted]`
while `<?php`, `namespace`, `use` lines, class header, and all
signatures are preserved verbatim in the original order. Before the
detection fix, auto-detection returned `UNKNOWN` (Perl prefilter
dominance) — reproduced and then verified fixed.
- Not tested: exotic PHP shapes (heredoc-heavy code, attributes `#[...]`
on methods, interleaved multi-`<?php ?>` HTML templates beyond the basic
mixed case); these fall back to verbatim preservation via the
uncaptured-node pass or malformed-passthrough, both of which are covered
by tests for the simple cases.

## Review Readiness

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
2026-07-31 15:54:13 -07:00
..
__init__.py Initial commit: Headroom SDK - LLM context optimization toolkit 2026-01-06 23:16:58 -08:00
test_code_compressor.py feat(code): add PHP support to CodeAwareCompressor (#2423) 2026-07-31 15:54:13 -07:00
test_code_compressor_cjk.py fix(code-compressor): CJK-aware relevance-query symbol matching (#1747) 2026-07-07 12:49:26 -05:00
test_content_router.py fix(proxy): cold-start fast pass — defer only Kompress, not the whole pipeline (#2073) 2026-07-14 06:34:41 -04:00
test_detect_fallback_1123.py fix(deps): remediate dependency CVEs and publish SBOM (#1509) 2026-06-27 15:28:12 -07:00
test_diff_compressor.py fix(transforms): normalize diff compressor context (#1801) 2026-07-05 14:03:33 -07:00
test_diff_compressor_rust_parity.py feat(rust): retire python diff_compressor, ship rust-only via pyo3 2026-04-26 09:15:37 -07:00
test_html_extractor.py fix(tests): skip HTML extractor tests when trafilatura not installed 2026-01-31 15:39:55 -08:00
test_kompress_compressor.py fix(kompress): match all ONNX backends with startswith, not exact "onnx" (#2448) 2026-07-20 17:29:08 -07:00
test_kompress_deadline.py perf(compression): take large cold-start contexts off the synchronous kompress path (#1171) (#1298) 2026-06-23 10:48:06 -05:00
test_kompress_remote.py fix(transforms/kompress-remote): keep compress fail-open on malformed 200 (#2320) 2026-07-17 12:11:41 -07:00
test_kompress_size_gate.py perf(compression): take large cold-start contexts off the synchronous kompress path (#1171) (#1298) 2026-06-23 10:48:06 -05:00
test_ort_dylib.py fix(core): load ONNX Runtime dynamically so headroom._core imports on non-AVX2 x86-64 (#1715) 2026-07-14 13:25:41 -04:00
test_pipeline_waste_signal_limit.py fix(proxy): keep large compression results on the critical path (#296) (#1352) 2026-06-24 10:15:59 -05:00
test_read_lifecycle.py fix(read-lifecycle): persist STALE Read originals in the CCR store (#1488) 2026-06-28 14:50:45 -07:00
test_smart_crusher_attribution.py feat(transforms): attribute read_lifecycle + smart_crush tags (#249) 2026-06-11 11:51:26 -05:00
test_smart_crusher_audit_safe.py feat(compression): add audit-safe mode with protected pattern matching (#1899) 2026-07-09 09:39:35 -04:00
test_smart_crusher_bugs.py Marker-free lossless_only mode + gate opaque-blob CCR markers behind enable_ccr_marker (#1129) 2026-06-23 12:52:15 -05:00
test_smart_crusher_ccr_retrieve_exemption.py fix(proxy): stop re-compressing headroom_retrieve output and emitting unredeemable markers (#1323) 2026-06-25 10:11:42 -05:00
test_smart_crusher_ccr_roundtrip.py chore(rust): SmartCrusher CCR marker injection + walker unification 2026-04-27 20:25:22 -07:00
test_smart_crusher_lossless_default.py feat(rust): SmartCrusher PR4 — lossless-first default + CCR-Dropped restoration 2026-04-27 16:30:22 -07:00
test_smart_crusher_rust_parity.py feat(rust): SmartCrusher PR4 — lossless-first default + CCR-Dropped restoration 2026-04-27 16:30:22 -07:00
test_tag_protector.py fix: A9 — tag protector discards wrap on placeholder loss 2026-05-02 18:01:24 -07:00
test_text_crusher.py perf(compression): take large cold-start contexts off the synchronous kompress path (#1171) (#1298) 2026-06-23 10:48:06 -05:00
test_text_crusher_cjk_eval.py feat(text-crusher): CJK-aware segmentation + relevance via ICU (#1504) 2026-07-15 19:58:48 +00:00
test_text_crusher_parity.py perf(compression): take large cold-start contexts off the synchronous kompress path (#1171) (#1298) 2026-06-23 10:48:06 -05:00
test_text_crusher_routing.py fix(router): compact JSON evades compression via whitespace token counting (#1857) 2026-07-14 06:54:01 -04:00
test_tree_sitter_thread_safety.py fix(code): pin tree-sitter-language-pack <1.0.0 in [code] extra (#1219) 2026-07-15 20:54:25 +00:00