Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,34 +70,30 @@ Public grammar surface:

- `CanonicalDirective`
- `DirectiveKind`
- `ValidatedDirective`
- `match_canonical_directive_start(text, start)`
- `contains_multiple_canonical_directives(text)`
- `decompose_directive(text)`
- `validate_directive(text)`
- `render_directive(kind, /, **operands)`

Use this surface for exact canonical validation, canonical directive syntax
decomposition, or canonical directive string construction only.
Use this surface for exact canonical directive decomposition and
classification via `decompose_directive(...)`, shallow syntax-start detection,
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
prefix at a position; it does not decompose or accept a whole directive
- `contains_multiple_canonical_directives(...)` detects compound
directive-shaped structure only; it is not full validation
- decomposition exposes canonical syntax only
directive-shaped structure only; it is not full decomposition
- use `decompose_directive(text)` to determine whether text is a complete
canonical directive
- a non-`None` decomposition returns a `CanonicalDirective` with `kind`,
`operands`, and preserved accepted `text`
- `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
- `match_canonical_directive_start(...)` is only for shallow syntax detection
- 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
Expand All @@ -116,8 +112,7 @@ Boundary notes:
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`.
`render_directive(kind, /, **operands)`, not from `CanonicalDirective.text`.

### `engine.premise`

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "context-compiler"
version = "0.9.0dev6"
version = "0.9.0dev7"
description = "Deterministic conversational state engine for LLM applications."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
35 changes: 2 additions & 33 deletions src/context_compiler/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,6 @@ class DirectiveKind(StrEnum):
CLEAR_STATE = "clear_state"


@dataclass(frozen=True, slots=True)
class ValidatedDirective:
"""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


@dataclass(frozen=True, slots=True)
class CanonicalDirective:
"""Represent one parsed canonical directive and its named operands.
Expand Down Expand Up @@ -409,22 +396,6 @@ def decompose_directive(text: str) -> CanonicalDirective | None:
return None


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.
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:
return None
return ValidatedDirective(text=parsed.text, kind=parsed.kind)


def render_directive(kind: DirectiveKind, /, **operands: str) -> str:
"""Produce canonical directive text from a semantic kind and operands.

Expand Down Expand Up @@ -461,19 +432,17 @@ def render_directive(kind: DirectiveKind, /, **operands: str) -> str:

operand_view = MappingProxyType(normalized_operands)
rendered = spec.renderer(operand_view)
validated = validate_directive(rendered)
if validated is None or validated.kind is not normalized_kind:
decomposed = decompose_directive(rendered)
if decomposed is None or decomposed.kind is not normalized_kind:
raise ValueError(f"Operands do not produce a canonical {normalized_kind.value} directive.")
return rendered


__all__ = [
"CanonicalDirective",
"DirectiveKind",
"ValidatedDirective",
"contains_multiple_canonical_directives",
"decompose_directive",
"match_canonical_directive_start",
"render_directive",
"validate_directive",
]
10 changes: 0 additions & 10 deletions tests/_api_contract_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ def assert_shape(
assert actual_members == sorted(expected_members.keys())
return

if "kind" in shape and shape["kind"] == "validated_directive":
assert value == grammar.validate_directive(shape["text"])
return

if "kind" in shape and shape["kind"] == "canonical_directive":
assert value == grammar.decompose_directive(shape["text"])
return
Expand Down Expand Up @@ -370,12 +366,6 @@ def _validate_shape_spec(shape: object, label: str) -> None:
if kind == "engine_instance":
_assert_closed_keys(shape, {"kind"}, label)
return
if kind == "validated_directive":
_assert_closed_keys(shape, {"kind", "text", "directive_kind"}, label)
_require_fields(shape, {"kind", "text", "directive_kind"}, label)
_assert_type(shape["text"], str, f"{label}.text")
_assert_type(shape["directive_kind"], str, f"{label}.directive_kind")
return
if kind == "canonical_directive":
_assert_closed_keys(shape, {"kind", "text", "directive_kind", "operands"}, label)
_require_fields(shape, {"kind", "text", "directive_kind", "operands"}, label)
Expand Down
39 changes: 1 addition & 38 deletions tests/fixtures/conformance/api/public-grammar-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
"names": [
"CanonicalDirective",
"DirectiveKind",
"ValidatedDirective",
"contains_multiple_canonical_directives",
"decompose_directive",
"match_canonical_directive_start",
"render_directive",
"validate_directive"
"render_directive"
],
"members": {
"CanonicalDirective": {
Expand All @@ -20,9 +18,6 @@
"DirectiveKind": {
"kind": "class"
},
"ValidatedDirective": {
"kind": "class"
},
"contains_multiple_canonical_directives": {
"kind": "callable",
"signature": {
Expand Down Expand Up @@ -155,38 +150,6 @@
}
}
]
},
"validate_directive": {
"kind": "callable",
"signature": {
"params": [
{
"name": "text",
"kind": "POSITIONAL_OR_KEYWORD",
"has_default": false
}
]
},
"shape_probes": [
{
"kwargs": {
"text": "use docker"
},
"return_shape": {
"kind": "validated_directive",
"text": "use docker",
"directive_kind": "use_item"
}
},
{
"kwargs": {
"text": "please use docker"
},
"return_shape": {
"type": "null"
}
}
]
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "grammar_decompose_boundary_whitespace_trim_use",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": " use docker "
},
"expected": {
"directive": {
"text": " use docker ",
"kind": "use_item",
"operands": {
"item": "docker"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "grammar_decompose_change_premise",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": "change premise to concise replies"
},
"expected": {
"directive": {
"text": "change premise to concise replies",
"kind": "change_premise",
"operands": {
"value": "concise replies"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"id": "grammar_validate_compound_rejected",
"id": "grammar_decompose_compound_rejected",
"kind": "grammar",
"action": {
"fn": "validate_directive",
"fn": "decompose_directive",
"text": "use docker and prohibit peanuts"
},
"expected": {
"validated": null
"directive": null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "grammar_decompose_invalid_compound_use_and_prohibit_rejected",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": "use docker and prohibit peanuts"
},
"expected": {
"directive": null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "grammar_decompose_invalid_missing_use_operand_rejected",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": "use"
},
"expected": {
"directive": null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "grammar_decompose_invalid_quoted_compound_rejected",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": "use \"docker and prohibit peanuts\""
},
"expected": {
"directive": null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "grammar_decompose_invalid_replacement_missing_new_rejected",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": "use instead of docker"
},
"expected": {
"directive": null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "grammar_decompose_invalid_replacement_missing_old_rejected",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": "use podman instead of"
},
"expected": {
"directive": null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "grammar_decompose_invalid_set_premise_to_rejected",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": "set premise to concise"
},
"expected": {
"directive": null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "grammar_decompose_keyword_case_normalization_use",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": "Use Docker"
},
"expected": {
"directive": {
"text": "Use Docker",
"kind": "use_item",
"operands": {
"item": "Docker"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "grammar_decompose_set_premise",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": "set premise concise replies"
},
"expected": {
"directive": {
"text": "set premise concise replies",
"kind": "set_premise",
"operands": {
"value": "concise replies"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "grammar_decompose_tab_separator_use",
"kind": "grammar",
"action": {
"fn": "decompose_directive",
"text": "use\tdocker"
},
"expected": {
"directive": {
"text": "use\tdocker",
"kind": "use_item",
"operands": {
"item": "docker"
}
}
}
}
Loading
Loading