diff --git a/docs/DescriptionAndMilestones.md b/docs/DescriptionAndMilestones.md index 9c80e21..a0811de 100644 --- a/docs/DescriptionAndMilestones.md +++ b/docs/DescriptionAndMilestones.md @@ -93,7 +93,7 @@ Long-term memory remains a host persistence responsibility, not an engine-owned ### 0.6.x -The 0.6.x line completed checkpoint support, precompiler boundary hardening, and +The 0.6.x line completed checkpoint support, preprocessor boundary hardening, and regression/conformance surfaces that prepare the project for the next milestone. ### 0.7 — Auditability & Boundary Hardening @@ -119,7 +119,7 @@ Make engine behavior inspectable and externally controllable without guessing. - requires `--with-precompiler` - never implicit - inspectable via preview / JSON output -- Explicit precompiler policy for multi-line, multi-sentence, and conversational-prefix input +- Explicit preprocessor policy for multi-line, multi-sentence, and conversational-prefix input (for example `ok. prohibit peanuts`, `sure - use docker`, mixed conversational + directive content) that is rule-based, fixture-covered, and inspectable - Define policy for directive-adjacent mixed-intent payloads @@ -131,7 +131,7 @@ Make engine behavior inspectable and externally controllable without guessing. - No expansion of authoritative state model - No implicit behavior - No heuristic-heavy parsing -- Preserve separation between engine, precompiler, and host/controller layers +- Preserve separation between engine, preprocessor, and host/controller layers ### Post-0.7 Direction @@ -145,7 +145,7 @@ Conceptual completion is a stable minimal contract, not feature accumulation. - Stable minimal engine contract - Deterministic and inspectable behavior -- Strict compiler / precompiler / host separation +- Strict compiler / preprocessor / host separation - No implicit behavior - No authoritative state-model expansion - Cross-language consistency with Python as source of truth diff --git a/examples/integrations/litellm/with_preprocessor.py b/examples/integrations/litellm/with_preprocessor.py index 18d2f62..b250a37 100644 --- a/examples/integrations/litellm/with_preprocessor.py +++ b/examples/integrations/litellm/with_preprocessor.py @@ -28,7 +28,7 @@ from context_compiler.engine import Engine from experimental.preprocessor import ( PRECOMPILE_OUTCOME_DIRECTIVE, - parse_precompiler_output, + parse_preprocessor_output, precompile_heuristic, render_prompt, ) @@ -236,7 +236,7 @@ def _llm_fallback_precompile(message: str, state: State) -> str | None: except Exception: return None - parsed = parse_precompiler_output(raw_output, source_input=message) + parsed = parse_preprocessor_output(raw_output, source_input=message) if parsed is None: return None return parsed @@ -251,7 +251,7 @@ def _precompile_user_input(message: str, state: State) -> str | None: heuristic_result["outcome"] == PRECOMPILE_OUTCOME_DIRECTIVE and heuristic_result["directive"] ): - parsed = parse_precompiler_output(heuristic_result["directive"]) + parsed = parse_preprocessor_output(heuristic_result["directive"]) logger.debug("preprocessor: heuristic_directive=%r", heuristic_result["directive"]) if parsed is not None: return parsed diff --git a/examples/integrations/litellm_proxy/context_compiler_precall_hook_with_preprocessor.py b/examples/integrations/litellm_proxy/context_compiler_precall_hook_with_preprocessor.py index 5f358f5..27acbcc 100644 --- a/examples/integrations/litellm_proxy/context_compiler_precall_hook_with_preprocessor.py +++ b/examples/integrations/litellm_proxy/context_compiler_precall_hook_with_preprocessor.py @@ -34,7 +34,7 @@ class CustomLogger: # type: ignore[no-redef] ) from experimental.preprocessor import ( PRECOMPILE_OUTCOME_DIRECTIVE, - parse_precompiler_output, + parse_preprocessor_output, precompile_heuristic, render_prompt, ) @@ -180,7 +180,7 @@ def _llm_fallback_precompile(message: str, state: State) -> str | None: except Exception: return None - parsed = parse_precompiler_output(raw_output, source_input=message) + parsed = parse_preprocessor_output(raw_output, source_input=message) if parsed is None: return None return parsed @@ -203,7 +203,7 @@ def _precompile_last_user_message(message: str, state: State | None) -> str | No heuristic_result["outcome"] == PRECOMPILE_OUTCOME_DIRECTIVE and heuristic_result["directive"] ): - parsed = parse_precompiler_output(heuristic_result["directive"]) + parsed = parse_preprocessor_output(heuristic_result["directive"]) if parsed is not None: return parsed except Exception: diff --git a/examples/integrations/openwebui/open_webui_pipe_with_preprocessor.py b/examples/integrations/openwebui/open_webui_pipe_with_preprocessor.py index 116ffa4..d943a28 100644 --- a/examples/integrations/openwebui/open_webui_pipe_with_preprocessor.py +++ b/examples/integrations/openwebui/open_webui_pipe_with_preprocessor.py @@ -51,7 +51,7 @@ def Field(*, default: Any, description: str = "") -> Any: # type: ignore[no-red from context_compiler.engine import Engine from experimental.preprocessor import ( PRECOMPILE_OUTCOME_DIRECTIVE, - parse_precompiler_output, + parse_preprocessor_output, precompile_heuristic, render_prompt, ) @@ -671,7 +671,7 @@ async def _llm_fallback_precompile( return None, normalized_error raw_output = _extract_completion_content(response) - parsed = parse_precompiler_output(raw_output, source_input=message) + parsed = parse_preprocessor_output(raw_output, source_input=message) if parsed is None: return None, None return parsed, None @@ -694,7 +694,7 @@ async def _precompile_user_input( heuristic_result["outcome"] == PRECOMPILE_OUTCOME_DIRECTIVE and heuristic_result["directive"] ): - parsed = parse_precompiler_output(heuristic_result["directive"]) + parsed = parse_preprocessor_output(heuristic_result["directive"]) if parsed is not None: return parsed, None diff --git a/experimental/preprocessor/README.md b/experimental/preprocessor/README.md index e079f48..d46de13 100644 --- a/experimental/preprocessor/README.md +++ b/experimental/preprocessor/README.md @@ -11,6 +11,11 @@ Recommended install for integrations using this package: Integrations should import this package from the installed environment rather than using repo-relative preprocessor paths. +Compatibility note: +- Prefer `heuristic_preprocessor.py` and `parse_preprocessor_output(...)`. +- `heuristic_precompiler.py` and `parse_precompiler_output(...)` remain + supported compatibility aliases in 0.6.x. + ## Modules - `heuristic_preprocessor.py`: conservative structural preprocessing pass. diff --git a/experimental/preprocessor/output_validation.py b/experimental/preprocessor/output_validation.py index 088fee6..b0bb646 100644 --- a/experimental/preprocessor/output_validation.py +++ b/experimental/preprocessor/output_validation.py @@ -1,9 +1,9 @@ """Shared preprocessor output normalization and validation helpers. Public API: -- validate_precompiler_output -- parse_precompiler_output - parse_preprocessor_output +- parse_precompiler_output (compatibility alias) +- validate_precompiler_output Internal helpers are implementation details and may change. """ @@ -146,7 +146,7 @@ def _validate_text_output(raw_output: str) -> PrecompilerValidationResult: def validate_precompiler_output( raw_output: object, *, source_input: str | None = None ) -> PrecompilerValidationResult: - """Validate raw precompiler output into a strict classification/output result. + """Validate raw preprocessor output into a strict classification/output result. Contract: - directive: output is a canonical directive string diff --git a/pyproject.toml b/pyproject.toml index d2c65f6..2bed5ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "context-compiler" -version = "0.6.15" +version = "0.6.16" description = "Deterministic conversational state engine for LLM applications." readme = "README.md" requires-python = ">=3.11" diff --git a/src/context_compiler/repl.py b/src/context_compiler/repl.py index 78f1e88..fc7eab7 100644 --- a/src/context_compiler/repl.py +++ b/src/context_compiler/repl.py @@ -1,7 +1,7 @@ import sys from typing import TextIO -from experimental.preprocessor.output_validation import parse_precompiler_output +from experimental.preprocessor.output_validation import parse_preprocessor_output from . import __version__, create_engine, get_policy_items, get_premise_value from .engine import Decision, DecisionKind, Engine, State @@ -107,7 +107,7 @@ def _compile_input(raw_input: str, engine: Engine, *, use_precompiler: bool) -> return raw_input if _has_pending_clarification(engine): return raw_input - parsed = parse_precompiler_output(raw_input, source_input=raw_input) + parsed = parse_preprocessor_output(raw_input, source_input=raw_input) return parsed if parsed is not None else raw_input diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index 4cbfb42..96e5261 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -1,16 +1,19 @@ -# Conformance Fixtures +# Fixture Suites -These fixtures define the cross-language conformance contract for the Context Compiler. +This directory contains multiple fixture suites with different contracts. -## Layout +## Fixture types -[`conformance/`](conformance/) +* [`conformance/`](conformance/) — core engine cross-language conformance contract. +* [`engine-regression/structured/`](engine-regression/structured/) — deterministic per-turn engine regression fixtures (including checkpoint snapshots). +* [`preprocessor/`](preprocessor/) — preprocessor heuristic and validation fixtures. -* [`step/`](conformance/step/) -* [`transcript/`](conformance/transcript/) +`conformance/` and `engine-regression/structured/` both cover engine behavior at different layers; preprocessor fixtures are intentionally separate from the core engine conformance contract. ## Step fixtures +For [`conformance/step/`](conformance/step/): + Each step fixture runs: 1. optional `prelude` (array of prior user inputs) @@ -27,6 +30,8 @@ Then asserts: ## Transcript fixtures +For [`conformance/transcript/`](conformance/transcript/): + Replay messages using `compile_transcript(messages)`. Results are normalized to: @@ -36,6 +41,8 @@ Results are normalized to: ## Prompt matching +For conformance transcript fixtures: + * If `prompt_to_user` is a string → exact match * If `prompt_to_user` is `null` → any non-empty string is accepted @@ -43,6 +50,35 @@ Results are normalized to: Fixtures reflect current Python behavior and tests. +## Engine regression fixtures + +[`engine-regression/structured/`](engine-regression/structured/) + +These fixtures capture deterministic per-turn engine behavior, including checkpoint snapshots, and are exercised by [`tests/test_structured_regression.py`](../test_structured_regression.py). + +They validate: + +* per-turn input handling +* `Decision.kind` outcomes +* clarification prompt behavior +* checkpoint export parity against expected snapshots +* continuation state restoration from checkpoints + +## Preprocessor fixtures + +[`preprocessor/`](preprocessor/) + +These fixtures cover preprocessor behavior (heuristic classification plus output validation), separate from the core engine conformance contract above. + +They are exercised by [`tests/test_preprocessor_conformance.py`](../test_preprocessor_conformance.py), including deterministic replay and validation-boundary checks (only validated directive output may pass through). + +They validate: + +* heuristic classification determinism +* directive extraction and normalization +* output validation boundaries +* reject/unknown safety handling for ambiguous and near-miss inputs + ## Test runner See [`tests/test_fixtures.py`](../test_fixtures.py) for execution details. diff --git a/tests/fixtures/engine-regression/structured/README.md b/tests/fixtures/engine-regression/structured/README.md index 853b3f5..800e5e7 100644 --- a/tests/fixtures/engine-regression/structured/README.md +++ b/tests/fixtures/engine-regression/structured/README.md @@ -48,12 +48,12 @@ They do **not** cover: - REPL / user-facing formatting - LLM integration behavior -- precompiler / heuristic directive generation +- preprocessor / heuristic directive generation These surfaces are tested separately because: - REPL output may intentionally differ from the underlying state representation -- precompiler behavior is non-deterministic and outside the engine contract +- preprocessor behavior is non-deterministic and outside the engine contract This fixture set is the **canonical engine-level conformance surface**, and may be reused by other implementations (e.g., TypeScript) to validate identical engine behavior. diff --git a/tests/fixtures/precompiler/admin-alias-remove-policies-unknown.json b/tests/fixtures/preprocessor/admin-alias-remove-policies-unknown.json similarity index 100% rename from tests/fixtures/precompiler/admin-alias-remove-policies-unknown.json rename to tests/fixtures/preprocessor/admin-alias-remove-policies-unknown.json diff --git a/tests/fixtures/precompiler/admin-alias-reset-policy-unknown.json b/tests/fixtures/preprocessor/admin-alias-reset-policy-unknown.json similarity index 100% rename from tests/fixtures/precompiler/admin-alias-reset-policy-unknown.json rename to tests/fixtures/preprocessor/admin-alias-reset-policy-unknown.json diff --git a/tests/fixtures/precompiler/ambiguous-directive-adjacent.json b/tests/fixtures/preprocessor/ambiguous-directive-adjacent.json similarity index 100% rename from tests/fixtures/precompiler/ambiguous-directive-adjacent.json rename to tests/fixtures/preprocessor/ambiguous-directive-adjacent.json diff --git a/tests/fixtures/precompiler/canonical-directive-clear-state.json b/tests/fixtures/preprocessor/canonical-directive-clear-state.json similarity index 100% rename from tests/fixtures/precompiler/canonical-directive-clear-state.json rename to tests/fixtures/preprocessor/canonical-directive-clear-state.json diff --git a/tests/fixtures/precompiler/canonical-directive-prohibit-peanuts.json b/tests/fixtures/preprocessor/canonical-directive-prohibit-peanuts.json similarity index 100% rename from tests/fixtures/precompiler/canonical-directive-prohibit-peanuts.json rename to tests/fixtures/preprocessor/canonical-directive-prohibit-peanuts.json diff --git a/tests/fixtures/precompiler/malformed-replacement-instead-docker-unknown.json b/tests/fixtures/preprocessor/malformed-replacement-instead-docker-unknown.json similarity index 100% rename from tests/fixtures/precompiler/malformed-replacement-instead-docker-unknown.json rename to tests/fixtures/preprocessor/malformed-replacement-instead-docker-unknown.json diff --git a/tests/fixtures/precompiler/mixed-intent-unknown.json b/tests/fixtures/preprocessor/mixed-intent-unknown.json similarity index 100% rename from tests/fixtures/precompiler/mixed-intent-unknown.json rename to tests/fixtures/preprocessor/mixed-intent-unknown.json diff --git a/tests/fixtures/precompiler/modal-please-clear-state-unknown.json b/tests/fixtures/preprocessor/modal-please-clear-state-unknown.json similarity index 100% rename from tests/fixtures/precompiler/modal-please-clear-state-unknown.json rename to tests/fixtures/preprocessor/modal-please-clear-state-unknown.json diff --git a/tests/fixtures/precompiler/natural-language-dont-use-unknown.json b/tests/fixtures/preprocessor/natural-language-dont-use-unknown.json similarity index 100% rename from tests/fixtures/precompiler/natural-language-dont-use-unknown.json rename to tests/fixtures/preprocessor/natural-language-dont-use-unknown.json diff --git a/tests/fixtures/precompiler/near-miss-change-premise-missing-to-unknown.json b/tests/fixtures/preprocessor/near-miss-change-premise-missing-to-unknown.json similarity index 100% rename from tests/fixtures/precompiler/near-miss-change-premise-missing-to-unknown.json rename to tests/fixtures/preprocessor/near-miss-change-premise-missing-to-unknown.json diff --git a/tests/fixtures/precompiler/near-miss-change-premise-to-empty-unknown.json b/tests/fixtures/preprocessor/near-miss-change-premise-to-empty-unknown.json similarity index 100% rename from tests/fixtures/precompiler/near-miss-change-premise-to-empty-unknown.json rename to tests/fixtures/preprocessor/near-miss-change-premise-to-empty-unknown.json diff --git a/tests/fixtures/precompiler/near-miss-prohibit-empty-unknown.json b/tests/fixtures/preprocessor/near-miss-prohibit-empty-unknown.json similarity index 100% rename from tests/fixtures/precompiler/near-miss-prohibit-empty-unknown.json rename to tests/fixtures/preprocessor/near-miss-prohibit-empty-unknown.json diff --git a/tests/fixtures/precompiler/near-miss-remove-policy-empty-unknown.json b/tests/fixtures/preprocessor/near-miss-remove-policy-empty-unknown.json similarity index 100% rename from tests/fixtures/precompiler/near-miss-remove-policy-empty-unknown.json rename to tests/fixtures/preprocessor/near-miss-remove-policy-empty-unknown.json diff --git a/tests/fixtures/precompiler/near-miss-set-premise-empty-unknown.json b/tests/fixtures/preprocessor/near-miss-set-premise-empty-unknown.json similarity index 100% rename from tests/fixtures/precompiler/near-miss-set-premise-empty-unknown.json rename to tests/fixtures/preprocessor/near-miss-set-premise-empty-unknown.json diff --git a/tests/fixtures/precompiler/near-miss-set-premise-to-unknown.json b/tests/fixtures/preprocessor/near-miss-set-premise-to-unknown.json similarity index 100% rename from tests/fixtures/precompiler/near-miss-set-premise-to-unknown.json rename to tests/fixtures/preprocessor/near-miss-set-premise-to-unknown.json diff --git a/tests/fixtures/precompiler/near-miss-use-empty-unknown.json b/tests/fixtures/preprocessor/near-miss-use-empty-unknown.json similarity index 100% rename from tests/fixtures/precompiler/near-miss-use-empty-unknown.json rename to tests/fixtures/preprocessor/near-miss-use-empty-unknown.json diff --git a/tests/fixtures/precompiler/near-miss-use-instead-of-missing-new-item-unknown.json b/tests/fixtures/preprocessor/near-miss-use-instead-of-missing-new-item-unknown.json similarity index 100% rename from tests/fixtures/precompiler/near-miss-use-instead-of-missing-new-item-unknown.json rename to tests/fixtures/preprocessor/near-miss-use-instead-of-missing-new-item-unknown.json diff --git a/tests/fixtures/precompiler/near-miss-use-instead-of-missing-old-item-unknown.json b/tests/fixtures/preprocessor/near-miss-use-instead-of-missing-old-item-unknown.json similarity index 100% rename from tests/fixtures/precompiler/near-miss-use-instead-of-missing-old-item-unknown.json rename to tests/fixtures/preprocessor/near-miss-use-instead-of-missing-old-item-unknown.json diff --git a/tests/fixtures/precompiler/ordinary-non-directive.json b/tests/fixtures/preprocessor/ordinary-non-directive.json similarity index 100% rename from tests/fixtures/precompiler/ordinary-non-directive.json rename to tests/fixtures/preprocessor/ordinary-non-directive.json diff --git a/tests/fixtures/precompiler/question-use-docker-unknown.json b/tests/fixtures/preprocessor/question-use-docker-unknown.json similarity index 100% rename from tests/fixtures/precompiler/question-use-docker-unknown.json rename to tests/fixtures/preprocessor/question-use-docker-unknown.json diff --git a/tests/fixtures/precompiler/quoted-exact-use-docker-backtick-unknown.json b/tests/fixtures/preprocessor/quoted-exact-use-docker-backtick-unknown.json similarity index 100% rename from tests/fixtures/precompiler/quoted-exact-use-docker-backtick-unknown.json rename to tests/fixtures/preprocessor/quoted-exact-use-docker-backtick-unknown.json diff --git a/tests/fixtures/precompiler/quoted-exact-use-docker-single-unknown.json b/tests/fixtures/preprocessor/quoted-exact-use-docker-single-unknown.json similarity index 100% rename from tests/fixtures/precompiler/quoted-exact-use-docker-single-unknown.json rename to tests/fixtures/preprocessor/quoted-exact-use-docker-single-unknown.json diff --git a/tests/fixtures/precompiler/quoted-exact-use-docker-unknown.json b/tests/fixtures/preprocessor/quoted-exact-use-docker-unknown.json similarity index 100% rename from tests/fixtures/precompiler/quoted-exact-use-docker-unknown.json rename to tests/fixtures/preprocessor/quoted-exact-use-docker-unknown.json diff --git a/tests/fixtures/precompiler/quoted-reported-unknown.json b/tests/fixtures/preprocessor/quoted-reported-unknown.json similarity index 100% rename from tests/fixtures/precompiler/quoted-reported-unknown.json rename to tests/fixtures/preprocessor/quoted-reported-unknown.json diff --git a/tests/fixtures/precompiler/unsupported-alias-unknown.json b/tests/fixtures/preprocessor/unsupported-alias-unknown.json similarity index 100% rename from tests/fixtures/precompiler/unsupported-alias-unknown.json rename to tests/fixtures/preprocessor/unsupported-alias-unknown.json diff --git a/tests/test_litellm_preprocessor_model_config.py b/tests/test_litellm_preprocessor_model_config.py index 190689b..3858f2c 100644 --- a/tests/test_litellm_preprocessor_model_config.py +++ b/tests/test_litellm_preprocessor_model_config.py @@ -52,7 +52,7 @@ def _completion(**kwargs): monkeypatch.setattr(module, "_get_litellm_completion", lambda: _completion) monkeypatch.setattr(module, "render_prompt", lambda *_: "prompt") - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) result = module._llm_fallback_precompile("please use docker", None) @@ -75,7 +75,7 @@ def _completion(**kwargs): monkeypatch.setattr(module, "_get_litellm_completion", lambda: _completion) monkeypatch.setattr(module, "render_prompt", lambda *_: "prompt") - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) result = module._llm_fallback_precompile("please use docker", None) @@ -100,7 +100,7 @@ def _completion(**kwargs): monkeypatch.setattr(module, "_get_litellm_completion", lambda: _completion) monkeypatch.setattr(module, "render_prompt", lambda *_: "prompt") - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) result = module._llm_fallback_precompile("please use docker", None) @@ -125,7 +125,7 @@ def _completion(**kwargs): monkeypatch.setattr(module, "_get_litellm_completion", lambda: _completion) monkeypatch.setattr(module, "render_prompt", lambda *_: "prompt") - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) result = module._llm_fallback_precompile("please use docker", None) diff --git a/tests/test_openwebui_preprocessor_pipe.py b/tests/test_openwebui_preprocessor_pipe.py index 0132212..c09059b 100644 --- a/tests/test_openwebui_preprocessor_pipe.py +++ b/tests/test_openwebui_preprocessor_pipe.py @@ -184,7 +184,7 @@ async def _chat_completion(_: object, payload: dict[str, Any], __: object) -> di "outcome": module.PRECOMPILE_OUTCOME_DIRECTIVE, "directive": "use docker", } - module.parse_precompiler_output = lambda value, **_kwargs: value + module.parse_preprocessor_output = lambda value, **_kwargs: value result = asyncio.run( pipe.pipe( @@ -533,7 +533,7 @@ def _create_engine(): monkeypatch.setattr(module, "create_engine", _create_engine) monkeypatch.setattr(module, "precompile_heuristic", lambda _text: {"outcome": "no_directive"}) - monkeypatch.setattr(module, "parse_precompiler_output", lambda _value, **_kwargs: None) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda _value, **_kwargs: None) pipe = module.Pipe() pipe.valves.BASE_MODEL_ID = "base-model" @@ -592,7 +592,7 @@ def test_preprocessor_pipe_normal_update_forwards_with_state_and_persists_checkp } ), ) - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) forwarded_payloads: list[dict[str, object]] = [] @@ -1122,7 +1122,7 @@ async def _chat_completion(_: object, payload: dict[str, Any], __: object) -> di monkeypatch.setattr(module, "generate_chat_completion", _chat_completion) monkeypatch.setattr(module, "precompile_heuristic", lambda _text: {"outcome": "no_directive"}) - monkeypatch.setattr(module, "parse_precompiler_output", lambda _value, **_kwargs: None) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda _value, **_kwargs: None) pipe = module.Pipe() pipe.valves.BASE_MODEL_ID = "base-model" @@ -1158,7 +1158,7 @@ async def _chat_completion(_: object, payload: dict[str, Any], __: object) -> di "directive": "prohibit peanuts", }, ) - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) pipe = module.Pipe() pipe.valves.BASE_MODEL_ID = "base-model" @@ -1198,7 +1198,7 @@ async def _chat_completion(_: object, payload: dict[str, Any], __: object) -> di monkeypatch.setattr(module, "generate_chat_completion", _chat_completion) monkeypatch.setattr(module, "precompile_heuristic", lambda _text: {"outcome": "no_directive"}) - monkeypatch.setattr(module, "parse_precompiler_output", lambda _value, **_kwargs: None) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda _value, **_kwargs: None) pipe = module.Pipe() pipe.valves.BASE_MODEL_ID = "base-model" @@ -1240,7 +1240,7 @@ async def _chat_completion(_: object, payload: dict[str, Any], __: object) -> An monkeypatch.setattr(module, "generate_chat_completion", _chat_completion) monkeypatch.setattr(module, "precompile_heuristic", lambda _text: {"outcome": "no_directive"}) - monkeypatch.setattr(module, "parse_precompiler_output", lambda _value, **_kwargs: None) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda _value, **_kwargs: None) pipe = module.Pipe() pipe.valves.BASE_MODEL_ID = "base-model" @@ -1345,7 +1345,7 @@ def _heuristic(text: str) -> dict[str, object]: return {"outcome": "no_directive", "directive": None} monkeypatch.setattr(module, "precompile_heuristic", _heuristic) - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) forwarded_payloads: list[dict[str, object]] = [] @@ -1416,7 +1416,7 @@ def _heuristic(text: str) -> dict[str, object]: return {"outcome": "no_directive", "directive": None} monkeypatch.setattr(module, "precompile_heuristic", _heuristic) - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) forwarded_payloads: list[dict[str, object]] = [] @@ -1509,7 +1509,7 @@ def test_preprocessor_pipe_trace_update_clear_reset_paths_single_and_consistent( "precompile_heuristic", lambda _text: {"outcome": "no_directive", "directive": None}, ) - monkeypatch.setattr(module, "parse_precompiler_output", lambda _value, **_kwargs: None) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda _value, **_kwargs: None) downstream_calls = 0 @@ -1566,7 +1566,7 @@ def test_preprocessor_pipe_clear_state_trace_not_duplicated_when_model_echoes_hi "precompile_heuristic", lambda _text: {"outcome": "no_directive", "directive": None}, ) - monkeypatch.setattr(module, "parse_precompiler_output", lambda _value, **_kwargs: None) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda _value, **_kwargs: None) async def _chat_completion( _: object, payload: dict[str, object], __: object @@ -1643,7 +1643,7 @@ def test_preprocessor_pipe_clear_state_strips_preexisting_contradictory_trace_fr "precompile_heuristic", lambda _text: {"outcome": "no_directive", "directive": None}, ) - monkeypatch.setattr(module, "parse_precompiler_output", lambda _value, **_kwargs: None) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda _value, **_kwargs: None) old_trace = ( "Context Compiler trace\n\n" @@ -1716,7 +1716,7 @@ def test_preprocessor_pipe_update_trace_and_injection_when_heuristic_emits_direc else {"outcome": "no_directive", "directive": None} ), ) - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) forwarded_payloads: list[dict[str, object]] = [] @@ -1785,7 +1785,7 @@ def test_preprocessor_pipe_replacement_update_trace_and_injection_when_heuristic ) ), ) - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) async def _chat_completion( _: object, @@ -1841,7 +1841,7 @@ def test_preprocessor_pipe_ambiguous_text_passthrough_trace_streaming(monkeypatc module._CHECKPOINTS_BY_CHAT_KEY.clear() monkeypatch.setattr(module, "precompile_heuristic", lambda _text: {"outcome": "no_directive"}) - monkeypatch.setattr(module, "parse_precompiler_output", lambda _value, **_kwargs: None) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda _value, **_kwargs: None) class _StreamingResponse: def __init__(self) -> None: @@ -1899,7 +1899,7 @@ def test_preprocessor_pipe_natural_language_abstention_can_passthrough_with_trac # Use the real heuristic behavior for this phrase (unknown/no_directive), # and force fallback to abstain so runtime stays conservative. - monkeypatch.setattr(module, "parse_precompiler_output", lambda _value, **_kwargs: None) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda _value, **_kwargs: None) async def _chat_completion( _: object, payload: dict[str, object], __: object @@ -1956,7 +1956,7 @@ def _heuristic(text: str) -> dict[str, object]: return {"outcome": "no_directive", "directive": None} monkeypatch.setattr(module, "precompile_heuristic", _heuristic) - monkeypatch.setattr(module, "parse_precompiler_output", lambda value, **_kwargs: value) + monkeypatch.setattr(module, "parse_preprocessor_output", lambda value, **_kwargs: value) downstream_calls = 0 diff --git a/tests/test_precompiler_conformance.py b/tests/test_preprocessor_conformance.py similarity index 90% rename from tests/test_precompiler_conformance.py rename to tests/test_preprocessor_conformance.py index 2916850..6b72c15 100644 --- a/tests/test_precompiler_conformance.py +++ b/tests/test_preprocessor_conformance.py @@ -2,14 +2,14 @@ import re from pathlib import Path -from experimental.preprocessor.heuristic_precompiler import precompile_heuristic +from experimental.preprocessor.heuristic_preprocessor import precompile_heuristic from experimental.preprocessor.output_validation import validate_precompiler_output -_PRECOMPILER_FIXTURES_DIR = Path(__file__).resolve().parent / "fixtures" / "precompiler" +_PREPROCESSOR_FIXTURES_DIR = Path(__file__).resolve().parent / "fixtures" / "preprocessor" def _fixture_paths() -> list[Path]: - return sorted(_PRECOMPILER_FIXTURES_DIR.glob("*.json")) + return sorted(_PREPROCESSOR_FIXTURES_DIR.glob("*.json")) def _load_fixture(path: Path) -> dict[str, object]: @@ -53,7 +53,7 @@ def _derived_risky_rewrite_candidates(source_input: str) -> list[str]: return candidates -def test_precompiler_conformance_fixtures() -> None: +def test_preprocessor_conformance_fixtures() -> None: for path in _fixture_paths(): fixture = _load_fixture(path) expected = fixture["expected"] @@ -71,7 +71,7 @@ def test_precompiler_conformance_fixtures() -> None: def test_engine_owned_near_misses_are_reject_only_for_fallback_rewrites() -> None: - # Engine-owned near-misses must not be canonicalized by the precompiler and + # Engine-owned near-misses must not be canonicalized by the preprocessor and # must remain unknown even if fallback proposes a plausible canonical rewrite. for path in _fixture_paths(): fixture = _load_fixture(path) diff --git a/tests/test_precompiler_heuristic.py b/tests/test_preprocessor_heuristic.py similarity index 99% rename from tests/test_precompiler_heuristic.py rename to tests/test_preprocessor_heuristic.py index 8b55649..62bf776 100644 --- a/tests/test_precompiler_heuristic.py +++ b/tests/test_preprocessor_heuristic.py @@ -1,6 +1,6 @@ import pytest -from experimental.preprocessor.heuristic_precompiler import precompile_heuristic +from experimental.preprocessor.heuristic_preprocessor import precompile_heuristic def test_heuristic_rejects_consistent_high_risk_non_directives() -> None: diff --git a/tests/test_precompiler_heuristic_properties.py b/tests/test_preprocessor_heuristic_properties.py similarity index 94% rename from tests/test_precompiler_heuristic_properties.py rename to tests/test_preprocessor_heuristic_properties.py index 92c9272..438a9ee 100644 --- a/tests/test_precompiler_heuristic_properties.py +++ b/tests/test_preprocessor_heuristic_properties.py @@ -8,10 +8,10 @@ PRECOMPILE_OUTCOME_NO_DIRECTIVE, PRECOMPILE_OUTCOME_UNKNOWN, ) -from experimental.preprocessor.heuristic_precompiler import precompile_heuristic +from experimental.preprocessor.heuristic_preprocessor import precompile_heuristic from experimental.preprocessor.output_validation import ( _is_allowed_directive, - parse_precompiler_output, + parse_preprocessor_output, ) CANONICAL_DIRECTIVES = [ @@ -48,7 +48,7 @@ def test_heuristic_accepts_canonical_directive_with_trailing_period_or_bang( ) -> None: result = precompile_heuristic(f"{directive}{punctuation}") assert result["outcome"] == PRECOMPILE_OUTCOME_DIRECTIVE - parsed = parse_precompiler_output(result["directive"]) + parsed = parse_preprocessor_output(result["directive"]) assert parsed == result["directive"] @@ -66,7 +66,7 @@ def test_heuristic_accepts_single_layer_exact_wrapper( left, right = wrapper result = precompile_heuristic(f"{left}{directive}{right}") assert result["outcome"] == PRECOMPILE_OUTCOME_DIRECTIVE - parsed = parse_precompiler_output(result["directive"]) + parsed = parse_preprocessor_output(result["directive"]) assert parsed == result["directive"] @@ -109,7 +109,7 @@ def test_heuristic_directive_output_is_always_validator_safe(message: str) -> No return directive = result["directive"] assert isinstance(directive, str) - assert parse_precompiler_output(directive) == directive + assert parse_preprocessor_output(directive) == directive @given(st.sampled_from(CANONICAL_DIRECTIVES), NON_EMPTY_TEXT, NON_EMPTY_TEXT) diff --git a/tests/test_precompiler_output_validation.py b/tests/test_preprocessor_output_validation.py similarity index 100% rename from tests/test_precompiler_output_validation.py rename to tests/test_preprocessor_output_validation.py diff --git a/tests/test_precompiler_validator_properties.py b/tests/test_preprocessor_validator_properties.py similarity index 90% rename from tests/test_precompiler_validator_properties.py rename to tests/test_preprocessor_validator_properties.py index 989a131..8a30631 100644 --- a/tests/test_precompiler_validator_properties.py +++ b/tests/test_preprocessor_validator_properties.py @@ -3,7 +3,7 @@ from experimental.preprocessor.output_validation import ( _is_allowed_directive, - parse_precompiler_output, + parse_preprocessor_output, validate_precompiler_output, ) @@ -45,14 +45,14 @@ ) ) def test_parse_non_string_never_produces_directive(raw_output: object) -> None: - assert parse_precompiler_output(raw_output) is None + assert parse_preprocessor_output(raw_output) is None @given(NOISY_TEXT) def test_parse_invalid_text_never_becomes_directive(text: str) -> None: stripped = text.strip() assume(not _is_allowed_directive(stripped)) - assert parse_precompiler_output(text) is None + assert parse_preprocessor_output(text) is None @given(st.sampled_from(CANONICAL_DIRECTIVES), SPACE_TEXT, SPACE_TEXT) @@ -60,7 +60,7 @@ def test_parse_valid_canonical_directive_always_passes( directive: str, leading_ws: str, trailing_ws: str ) -> None: raw = f"{leading_ws}{directive}{trailing_ws}" - assert parse_precompiler_output(raw) == directive + assert parse_preprocessor_output(raw) == directive @given(st.sampled_from(CANONICAL_DIRECTIVES), NON_EMPTY_TEXT, NON_EMPTY_TEXT) @@ -70,7 +70,7 @@ def test_parse_rejects_directive_with_surrounding_text( raw = f"{prefix} {directive} {suffix}" stripped = raw.strip() assume(not _is_allowed_directive(stripped)) - assert parse_precompiler_output(raw) is None + assert parse_preprocessor_output(raw) is None @given( @@ -87,7 +87,7 @@ def test_parse_rejects_directive_with_surrounding_text( ) def test_parse_rejects_directive_in_constrained_wrappers(directive: str, wrapper: str) -> None: wrapped = wrapper.format(directive) - assert parse_precompiler_output(wrapped) is None + assert parse_preprocessor_output(wrapped) is None def test_validate_malformed_abstain_negative_boundaries_are_unknown() -> None: @@ -116,13 +116,13 @@ def test_parse_rejects_near_miss_directives_when_wrapped_or_prefixed() -> None: 'he said "use docker"', ] for raw in cases: - assert parse_precompiler_output(raw) is None + assert parse_preprocessor_output(raw) is None @given(st.one_of(st.none(), st.integers(), st.text(max_size=120))) def test_parse_output_idempotent(raw_output: object) -> None: - first = parse_precompiler_output(raw_output) - second = parse_precompiler_output(first) + first = parse_preprocessor_output(raw_output) + second = parse_preprocessor_output(first) if first is None: assert second is None else: diff --git a/tests/test_repl.py b/tests/test_repl.py index 957f15a..42ca966 100644 --- a/tests/test_repl.py +++ b/tests/test_repl.py @@ -303,9 +303,9 @@ def _parse(raw_output: object, *, source_input: str | None = None) -> str | None seen.append((raw_output, source_input)) if raw_output == "use podman instead of docker": return "use podman instead of docker" - raise AssertionError("parse_precompiler_output should be bypassed while pending") + raise AssertionError("parse_preprocessor_output should be bypassed while pending") - monkeypatch.setattr(repl_module, "parse_precompiler_output", _parse) + monkeypatch.setattr(repl_module, "parse_preprocessor_output", _parse) out = StringIO() run_repl( @@ -323,9 +323,9 @@ def _parse(raw_output: object, *, source_input: str | None = None) -> str | None def test_repl_without_precompiler_does_not_parse_inputs(monkeypatch: pytest.MonkeyPatch) -> None: def _fail_parse(_raw: object, *, source_input: str | None = None) -> str | None: del source_input - raise AssertionError("parse_precompiler_output should not be called") + raise AssertionError("parse_preprocessor_output should not be called") - monkeypatch.setattr(repl_module, "parse_precompiler_output", _fail_parse) + monkeypatch.setattr(repl_module, "parse_preprocessor_output", _fail_parse) out = StringIO() run_repl(StringIO('{"classification":"directive","output":"prohibit peanuts"}\nquit\n'), out) diff --git a/uv.lock b/uv.lock index 16d5b5f..bfdfa14 100644 --- a/uv.lock +++ b/uv.lock @@ -468,7 +468,7 @@ wheels = [ [[package]] name = "context-compiler" -version = "0.6.15" +version = "0.6.16" source = { editable = "." } [package.optional-dependencies]