diff --git a/docs/api-reference.md b/docs/api-reference.md index cd5337c..d5530c8 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -71,9 +71,10 @@ Public grammar surface: - `CanonicalDirective` - `DirectiveKind` - `ValidatedDirective` +- `match_canonical_directive_start(text, start)` +- `contains_multiple_canonical_directives(text)` - `decompose_directive(text)` - `validate_directive(text)` -- `is_canonical_directive(text)` - `render_directive(kind, /, **operands)` Use this surface for exact canonical validation, canonical directive syntax @@ -81,10 +82,25 @@ decomposition, or canonical directive string construction only. Boundary notes: +- `match_canonical_directive_start(...)` only matches a canonical directive + prefix at a position; it does not validate a whole directive +- `contains_multiple_canonical_directives(...)` detects compound + directive-shaped structure only; it is not full validation - decomposition exposes canonical syntax only +- `CanonicalDirective.text` preserves the original accepted input text, so + caller casing or formatting may remain visible there +- `CanonicalDirective.text` is not canonical serialized directive text +- callers can treat `decompose_directive(...) is not None` as the complete + canonical-directive check when operand access is needed - operands are grammar-level text, not normalized semantic values +- `ValidatedDirective.text` preserves the accepted input text used for + classification +- callers can treat `validate_directive(...) is not None` as the + canonical-directive check when only classification is needed - validation returns `None` for any non-canonical input - decomposition returns `None` for any non-canonical input +- `render_directive(...)` produces canonical directive text from semantic kind + and operands - rendering is syntax-only and performs no state interpretation - `engine.step(...)` remains the authority for error, state transitions, and mutation behavior @@ -99,6 +115,9 @@ Boundary notes: `CanonicalDirective.operands` preserves the grammar-recognized operand text. Core does not lowercase operands, collapse internal operand whitespace, or convert operand text into engine/domain identifiers at the grammar layer. +Canonical serialized directive output comes from +`render_directive(kind, /, **operands)`, not from `CanonicalDirective.text` or +`ValidatedDirective.text`. ### `engine.premise` diff --git a/pyproject.toml b/pyproject.toml index eddd5c5..64d8a30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "context-compiler" -version = "0.9.0dev5" +version = "0.9.0dev6" description = "Deterministic conversational state engine for LLM applications." readme = "README.md" requires-python = ">=3.11" diff --git a/src/context_compiler/grammar.py b/src/context_compiler/grammar.py index 3b92a2a..735f109 100644 --- a/src/context_compiler/grammar.py +++ b/src/context_compiler/grammar.py @@ -23,7 +23,12 @@ class DirectiveKind(StrEnum): @dataclass(frozen=True, slots=True) class ValidatedDirective: - """Classify text as one canonical directive kind without exposing operands.""" + """Classify accepted input text as one canonical directive kind. + + ``text`` preserves the accepted input text used for classification rather + than a canonical rendered representation, so caller casing and formatting + may remain visible here. + """ text: str kind: DirectiveKind @@ -31,7 +36,13 @@ class ValidatedDirective: @dataclass(frozen=True, slots=True) class CanonicalDirective: - """Represent one parsed canonical directive and its named operands.""" + """Represent one parsed canonical directive and its named operands. + + ``text`` preserves the original accepted input text. It may retain caller + formatting or casing and is not canonical serialized directive text; use + :func:`render_directive` to produce canonical directive text from semantic + kind and operands. + """ text: str kind: DirectiveKind @@ -182,10 +193,11 @@ def _operand_starts_with_token(value: str, token: str) -> bool: def match_canonical_directive_start(text: str, start: int) -> int | None: """Locate a canonical directive prefix at a given character position. - This classifies whether canonical directive syntax begins at ``start`` and, - when it does, returns the index immediately after the directive keyword - prefix. It does not parse operands, validate the full directive payload, or - evaluate multi-directive state semantics. + This is a shallow syntax-start matcher: it classifies whether canonical + directive syntax begins at ``start`` and, when it does, returns the index + immediately after the directive keyword prefix. It does not parse operands, + validate the full directive payload, or evaluate multi-directive state + semantics. """ if start < 0 or start >= len(text): return None @@ -246,10 +258,11 @@ def _match_directive_token( def contains_multiple_canonical_directives(text: str) -> bool: """Report whether text contains more than one canonical directive start. - This detects compound directive text by looking for multiple canonical - directive prefixes in the same input. It does not parse directive operands, - repair malformed text, or determine whether a whole string should be - accepted as a single directive. + This detects compound directive structure by looking for multiple canonical + directive prefixes in the same input. It is not a replacement for full + directive validation, and it does not parse directive operands, repair + malformed text, or determine whether a whole string should be accepted as a + single directive. """ first_start = match_canonical_directive_start(text, 0) if first_start is None: @@ -290,8 +303,10 @@ def decompose_directive(text: str) -> CanonicalDirective | None: This determines whether ``text`` is a single canonical directive and, when it is, returns the directive kind plus canonical operand names with the - original operand text preserved. It does not repair input, infer intent, or - evaluate directive effects against compiler state. + original operand text preserved. Callers can determine whether ``text`` is + a complete canonical directive by checking whether this returns a non-`None` + result. It does not repair input, infer intent, or evaluate directive + effects against compiler state. """ trimmed_text = _trim_ascii_whitespace(text) if trimmed_text == "": @@ -398,9 +413,11 @@ def validate_directive(text: str) -> ValidatedDirective | None: """Classify whether text is a canonical directive. This determines whether ``text`` belongs to one canonical directive family - and returns only the normalized semantic kind needed for classification. It - does not expose operands, render directives, repair malformed text, or - evaluate any state transition. + and returns only the normalized semantic kind needed for classification. + Callers can determine whether ``text`` is a canonical directive by checking + whether this returns a non-`None` result while only receiving + classification information. It does not expose operands, render directives, + repair malformed text, or evaluate any state transition. """ parsed = decompose_directive(text) if parsed is None: @@ -408,18 +425,8 @@ def validate_directive(text: str) -> ValidatedDirective | None: return ValidatedDirective(text=parsed.text, kind=parsed.kind) -def is_canonical_directive(text: str) -> bool: - """Return whether text is exactly one canonical directive. - - This provides boolean directive classification for callers that only need a - yes-or-no answer. It does not parse operands, explain rejection reasons, or - perform validation beyond canonical grammar recognition. - """ - return validate_directive(text) is not None - - def render_directive(kind: DirectiveKind, /, **operands: str) -> str: - """Render canonical directive text from a semantic kind and operands. + """Produce canonical directive text from a semantic kind and operands. This determines the exact canonical spelling for an existing grammar capability and rejects operand combinations that would not round-trip as the @@ -466,7 +473,6 @@ def render_directive(kind: DirectiveKind, /, **operands: str) -> str: "ValidatedDirective", "contains_multiple_canonical_directives", "decompose_directive", - "is_canonical_directive", "match_canonical_directive_start", "render_directive", "validate_directive", diff --git a/tests/fixtures/conformance/api/public-grammar-v1.json b/tests/fixtures/conformance/api/public-grammar-v1.json index 169bee3..fe8ecd9 100644 --- a/tests/fixtures/conformance/api/public-grammar-v1.json +++ b/tests/fixtures/conformance/api/public-grammar-v1.json @@ -9,7 +9,6 @@ "ValidatedDirective", "contains_multiple_canonical_directives", "decompose_directive", - "is_canonical_directive", "match_canonical_directive_start", "render_directive", "validate_directive" @@ -91,29 +90,6 @@ } ] }, - "is_canonical_directive": { - "kind": "callable", - "signature": { - "params": [ - { - "name": "text", - "kind": "POSITIONAL_OR_KEYWORD", - "has_default": false - } - ] - }, - "shape_probes": [ - { - "kwargs": { - "text": "clear state" - }, - "return_shape": { - "type": "boolean", - "const": true - } - } - ] - }, "match_canonical_directive_start": { "kind": "callable", "signature": { diff --git a/tests/test_grammar.py b/tests/test_grammar.py index ae77411..fde4ee0 100644 --- a/tests/test_grammar.py +++ b/tests/test_grammar.py @@ -10,7 +10,6 @@ ValidatedDirective, contains_multiple_canonical_directives, decompose_directive, - is_canonical_directive, match_canonical_directive_start, render_directive, validate_directive, @@ -83,7 +82,6 @@ def test_validate_directive_accepts_each_canonical_family( ) -> None: validated = validate_directive(text) assert validated == ValidatedDirective(text=text, kind=expected_kind) - assert is_canonical_directive(text) is True @pytest.mark.parametrize( @@ -136,7 +134,6 @@ def test_decompose_directive_accepts_each_canonical_family( def test_validate_directive_rejects_non_canonical_inputs(text: str) -> None: assert validate_directive(text) is None assert decompose_directive(text) is None - assert is_canonical_directive(text) is False @pytest.mark.parametrize( @@ -290,7 +287,6 @@ def test_public_grammar_all_includes_semantic_surface() -> None: "ValidatedDirective", "contains_multiple_canonical_directives", "decompose_directive", - "is_canonical_directive", "match_canonical_directive_start", "render_directive", "validate_directive", diff --git a/tests/test_public_grammar_root_exports.py b/tests/test_public_grammar_root_exports.py index 73fd8fc..6b3076f 100644 --- a/tests/test_public_grammar_root_exports.py +++ b/tests/test_public_grammar_root_exports.py @@ -7,7 +7,6 @@ def test_root_does_not_export_public_grammar_surface() -> None: "DirectiveKind", "validate_directive", "render_directive", - "is_canonical_directive", ): assert name not in context_compiler.__all__ assert not hasattr(context_compiler, name) @@ -19,7 +18,6 @@ def test_grammar_submodule_preserves_public_grammar_surface() -> None: assert grammar_module.decompose_directive is not None assert grammar_module.validate_directive is not None assert grammar_module.render_directive is not None - assert grammar_module.is_canonical_directive is not None def test_root_does_not_export_private_grammar_implementation() -> None: diff --git a/uv.lock b/uv.lock index cfa3425..3538eae 100644 --- a/uv.lock +++ b/uv.lock @@ -296,7 +296,7 @@ wheels = [ [[package]] name = "context-compiler" -version = "0.9.0.dev5" +version = "0.9.0.dev6" source = { editable = "." } [package.optional-dependencies]