context-compiler 0.9.0.dev2__py3-none-any.whl → 0.9.0.dev4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  from importlib.metadata import version
2
2
 
3
3
  from .const import (
4
- DECISION_CLARIFY,
5
- DECISION_PASSTHROUGH,
4
+ DECISION_ERROR,
5
+ DECISION_NO_DIRECTIVE,
6
6
  DECISION_UPDATE,
7
7
  POLICY_PROHIBIT,
8
8
  POLICY_USE,
@@ -14,18 +14,20 @@ from .controller import (
14
14
  step,
15
15
  )
16
16
  from .decision_helpers import (
17
- get_clarify_prompt,
18
17
  get_decision_state,
19
- is_clarify,
20
- is_passthrough,
18
+ get_error_message,
19
+ is_error,
20
+ is_no_directive,
21
21
  is_update,
22
22
  )
23
23
  from .engine import (
24
24
  Decision,
25
- DecisionKind,
26
25
  Engine,
26
+ ErrorDecision,
27
+ NoDirectiveDecision,
27
28
  PolicyValue,
28
29
  State,
30
+ UpdateDecision,
29
31
  create_engine,
30
32
  )
31
33
 
@@ -33,23 +35,25 @@ __version__ = version("context-compiler")
33
35
 
34
36
  __all__ = [
35
37
  "Decision",
36
- "DecisionKind",
37
- "DECISION_CLARIFY",
38
- "DECISION_PASSTHROUGH",
38
+ "DECISION_ERROR",
39
+ "DECISION_NO_DIRECTIVE",
39
40
  "DECISION_UPDATE",
41
+ "ErrorDecision",
40
42
  "Engine",
43
+ "NoDirectiveDecision",
41
44
  "POLICY_PROHIBIT",
42
45
  "POLICY_USE",
43
46
  "PolicyValue",
44
47
  "State",
45
48
  "StepResult",
49
+ "UpdateDecision",
46
50
  "create_engine",
47
- "get_clarify_prompt",
51
+ "get_error_message",
48
52
  "get_decision_state",
49
53
  "get_step_decision",
50
54
  "get_step_state",
51
- "is_clarify",
52
- "is_passthrough",
55
+ "is_error",
56
+ "is_no_directive",
53
57
  "is_update",
54
58
  "step",
55
59
  ]
context_compiler/const.py CHANGED
@@ -12,9 +12,9 @@ POLICY_USE: Final = "use"
12
12
  POLICY_PROHIBIT: Final = "prohibit"
13
13
 
14
14
  # Decision kinds
15
- DECISION_PASSTHROUGH: Final = "passthrough"
15
+ DECISION_NO_DIRECTIVE: Final = "no_directive"
16
16
  DECISION_UPDATE: Final = "update"
17
- DECISION_CLARIFY: Final = "clarify"
17
+ DECISION_ERROR: Final = "error"
18
18
 
19
19
  # Schema version
20
20
  SCHEMA_VERSION: Final = 2
@@ -8,29 +8,39 @@ OUTPUT_VERSION: Literal[1] = 1
8
8
 
9
9
 
10
10
  class PremiseDiff(TypedDict):
11
+ """Describe how the authoritative premise changed between two states."""
12
+
11
13
  before: str | None
12
14
  after: str | None
13
15
  changed: bool
14
16
 
15
17
 
16
18
  class ChangedPolicyDiff(TypedDict):
19
+ """Capture the old and new value for one policy item."""
20
+
17
21
  before: Literal["use", "prohibit"]
18
22
  after: Literal["use", "prohibit"]
19
23
 
20
24
 
21
25
  class PoliciesDiff(TypedDict):
26
+ """Summarize policy items added, removed, or changed between two states."""
27
+
22
28
  added: dict[str, Literal["use", "prohibit"]]
23
29
  removed: dict[str, Literal["use", "prohibit"]]
24
30
  changed: dict[str, ChangedPolicyDiff]
25
31
 
26
32
 
27
33
  class StructuralDiff(TypedDict):
34
+ """Describe whether and how authoritative state changed structurally."""
35
+
28
36
  changed: bool
29
37
  premise: PremiseDiff
30
38
  policies: PoliciesDiff
31
39
 
32
40
 
33
41
  class StepResult(TypedDict):
42
+ """Return the committed outcome of one controller-driven engine step."""
43
+
34
44
  output_version: Literal[1]
35
45
  mode: Literal["step"]
36
46
  decision: Decision
@@ -38,6 +48,8 @@ class StepResult(TypedDict):
38
48
 
39
49
 
40
50
  class PreviewResult(TypedDict):
51
+ """Return the dry-run outcome of one controller preview evaluation."""
52
+
41
53
  output_version: Literal[1]
42
54
  mode: Literal["preview"]
43
55
  decision: Decision
@@ -48,30 +60,44 @@ class PreviewResult(TypedDict):
48
60
 
49
61
 
50
62
  def get_step_decision(step_result: StepResult) -> Decision:
63
+ """Return the decision emitted by a committed controller step."""
64
+
51
65
  return step_result["decision"]
52
66
 
53
67
 
54
68
  def get_step_state(step_result: StepResult) -> State:
69
+ """Return the authoritative state after a committed controller step."""
70
+
55
71
  return step_result["state"]
56
72
 
57
73
 
58
74
  def get_preview_decision(preview_result: PreviewResult) -> Decision:
75
+ """Return the decision that preview computed without mutating live state."""
76
+
59
77
  return preview_result["decision"]
60
78
 
61
79
 
62
80
  def get_preview_state_after(preview_result: PreviewResult) -> State:
81
+ """Return the simulated post-transition state from preview."""
82
+
63
83
  return preview_result["state_after"]
64
84
 
65
85
 
66
86
  def preview_would_mutate(preview_result: PreviewResult) -> bool:
87
+ """Return whether preview observed any structural state change."""
88
+
67
89
  return preview_result["would_mutate"]
68
90
 
69
91
 
70
92
  def diff_has_changes(diff: StructuralDiff) -> bool:
93
+ """Return whether a structural diff reports any state change."""
94
+
71
95
  return diff["changed"]
72
96
 
73
97
 
74
98
  def state_diff(before: State, after: State) -> StructuralDiff:
99
+ """Compute a structural diff between two authoritative engine states."""
100
+
75
101
  before_premise = before["premise"]
76
102
  after_premise = after["premise"]
77
103
  premise_changed = before_premise != after_premise
@@ -112,6 +138,8 @@ def state_diff(before: State, after: State) -> StructuralDiff:
112
138
 
113
139
 
114
140
  def step(engine: Engine, user_input: str) -> StepResult:
141
+ """Commit one engine transition and package the resulting decision and state."""
142
+
115
143
  decision = engine.step(user_input)
116
144
  return {
117
145
  "output_version": OUTPUT_VERSION,
@@ -122,19 +150,19 @@ def step(engine: Engine, user_input: str) -> StepResult:
122
150
 
123
151
 
124
152
  def preview(engine: Engine, user_input: str) -> PreviewResult:
125
- state_json = engine.export_json()
126
- state_before = engine.state
153
+ """Evaluate one transition without mutating the engine's live state.
127
154
 
128
- decision: Decision | None = None
129
- state_after: State | None = None
130
- try:
131
- decision = engine.step(user_input)
132
- state_after = engine.state
133
- finally:
134
- engine.import_json(state_json)
155
+ Preview uses the same transition evaluation path as committed execution,
156
+ then returns the simulated decision, before/after states, and a structural
157
+ diff that callers can inspect before deciding whether to step.
158
+ """
135
159
 
136
- assert decision is not None
137
- assert state_after is not None
160
+ state_before = engine.state
161
+ # Preview intentionally consumes the engine's private evaluator so preview and
162
+ # committed execution share one transition path without making evaluation public.
163
+ evaluated = engine._evaluate_transition(state_before, user_input) # noqa: SLF001
164
+ decision = evaluated.decision
165
+ state_after = evaluated.next_state
138
166
 
139
167
  diff = state_diff(state_before, state_after)
140
168
  would_mutate = diff["changed"]
@@ -1,24 +1,46 @@
1
1
  """Public helpers for safer decision inspection in host-side code."""
2
2
 
3
- from .const import DECISION_CLARIFY, DECISION_PASSTHROUGH, DECISION_UPDATE
4
- from .engine import Decision, State
3
+ from typing import TypeGuard
5
4
 
5
+ from .const import DECISION_ERROR, DECISION_NO_DIRECTIVE, DECISION_UPDATE
6
+ from .engine import (
7
+ Decision,
8
+ ErrorDecision,
9
+ NoDirectiveDecision,
10
+ State,
11
+ UpdateDecision,
12
+ )
13
+
14
+
15
+ def is_update(decision: Decision) -> TypeGuard[UpdateDecision]:
16
+ """Return whether a decision represents a successful state update."""
6
17
 
7
- def is_update(decision: Decision) -> bool:
8
18
  return decision["kind"] == DECISION_UPDATE
9
19
 
10
20
 
11
- def is_clarify(decision: Decision) -> bool:
12
- return decision["kind"] == DECISION_CLARIFY
21
+ def is_error(decision: Decision) -> TypeGuard[ErrorDecision]:
22
+ """Return whether a decision represents an error outcome."""
23
+
24
+ return decision["kind"] == DECISION_ERROR
13
25
 
14
26
 
15
- def is_passthrough(decision: Decision) -> bool:
16
- return decision["kind"] == DECISION_PASSTHROUGH
27
+ def is_no_directive(decision: Decision) -> TypeGuard[NoDirectiveDecision]:
28
+ """Return whether a decision reports that no directive was recognized."""
17
29
 
30
+ return decision["kind"] == DECISION_NO_DIRECTIVE
18
31
 
19
- def get_clarify_prompt(decision: Decision) -> str | None:
20
- return decision["prompt_to_user"]
32
+
33
+ def get_error_message(decision: Decision) -> str | None:
34
+ """Return the error message for an error decision, if present."""
35
+
36
+ if not is_error(decision):
37
+ return None
38
+ return decision["message"]
21
39
 
22
40
 
23
41
  def get_decision_state(decision: Decision) -> State | None:
42
+ """Return the updated authoritative state for an update decision, if present."""
43
+
44
+ if not is_update(decision):
45
+ return None
24
46
  return decision["state"]
@@ -5,11 +5,13 @@ import re
5
5
  from collections.abc import Mapping
6
6
  from copy import deepcopy
7
7
  from dataclasses import dataclass
8
- from enum import StrEnum
9
8
  from typing import Literal, TypedDict
10
9
  from unicodedata import normalize as unicode_normalize
11
10
 
12
11
  from .const import (
12
+ DECISION_ERROR,
13
+ DECISION_NO_DIRECTIVE,
14
+ DECISION_UPDATE,
13
15
  POLICY_PROHIBIT,
14
16
  POLICY_USE,
15
17
  SCHEMA_VERSION,
@@ -17,7 +19,7 @@ from .const import (
17
19
  STATE_PREMISE,
18
20
  STATE_VERSION,
19
21
  )
20
- from .grammar import DirectiveKind, _parse_canonical_directive
22
+ from .grammar import DirectiveKind, decompose_directive
21
23
 
22
24
  PolicyValue = Literal["use", "prohibit"]
23
25
 
@@ -30,20 +32,33 @@ class State(TypedDict):
30
32
  version: Literal[2]
31
33
 
32
34
 
33
- class DecisionKind(StrEnum):
34
- UPDATE = "update"
35
- PASSTHROUGH = "passthrough"
36
- CLARIFY = "clarify"
35
+ class NoDirectiveDecision(TypedDict):
36
+ """Report that input did not match any recognized canonical directive."""
37
37
 
38
+ kind: Literal["no_directive"]
38
39
 
39
- class Decision(TypedDict):
40
- kind: DecisionKind
41
- state: State | None
42
- prompt_to_user: str | None
40
+
41
+ class UpdateDecision(TypedDict):
42
+ """Report a successful transition and the resulting authoritative state."""
43
+
44
+ kind: Literal["update"]
45
+ state: State
46
+
47
+
48
+ class ErrorDecision(TypedDict):
49
+ """Report a rejected transition together with a user-facing error message."""
50
+
51
+ kind: Literal["error"]
52
+ message: str
53
+
54
+
55
+ Decision = NoDirectiveDecision | UpdateDecision | ErrorDecision
43
56
 
44
57
 
45
58
  @dataclass(frozen=True)
46
59
  class Action:
60
+ """Represent one parsed engine action before state validation or mutation."""
61
+
47
62
  kind: Literal[
48
63
  "set_premise",
49
64
  "change_premise",
@@ -61,65 +76,100 @@ class Action:
61
76
  old_item: str | None = None
62
77
 
63
78
 
64
- _PASSTHROUGH: Decision = {
65
- "kind": DecisionKind.PASSTHROUGH,
66
- "state": None,
67
- "prompt_to_user": None,
68
- }
79
+ @dataclass(frozen=True)
80
+ class _EvaluatedTransition:
81
+ decision: Decision
82
+ next_state: State
83
+
84
+
85
+ _NO_DIRECTIVE: NoDirectiveDecision = {"kind": DECISION_NO_DIRECTIVE}
69
86
 
70
87
 
71
88
  def create_engine(state: State | None = None) -> "Engine":
89
+ """Create an engine initialized from validated state or the empty state."""
90
+
72
91
  return Engine(state=state)
73
92
 
74
93
 
75
94
  class Engine:
95
+ """Own the authoritative state and apply one directive transition at a time."""
96
+
76
97
  def __init__(self, state: State | None = None) -> None:
77
98
  self._state: State
78
99
  self._replace_state(_initial_state() if state is None else _load_state_obj(state))
79
100
 
80
101
  @property
81
102
  def premise(self) -> str | None:
103
+ """Return the current premise from authoritative state."""
104
+
82
105
  return self._state[STATE_PREMISE]
83
106
 
84
107
  @property
85
108
  def policies(self) -> Mapping[str, PolicyValue]:
109
+ """Return a defensive copy of the current policy mapping."""
110
+
86
111
  return deepcopy(self._state[STATE_POLICIES])
87
112
 
88
113
  @property
89
114
  def state(self) -> State:
115
+ """Return a defensive copy of the full authoritative state."""
116
+
90
117
  return deepcopy(self._state)
91
118
 
92
119
  def export_json(self) -> str:
120
+ """Serialize the current authoritative state to canonical JSON text."""
121
+
93
122
  return json.dumps(self._state, sort_keys=True, separators=(",", ":"))
94
123
 
95
124
  def import_json(self, payload: str) -> None:
125
+ """Replace authoritative state from previously exported JSON text.
126
+
127
+ The payload must match the current state schema and is normalized using
128
+ the same validation rules applied to other engine state inputs.
129
+ """
130
+
96
131
  self._replace_state(_load_state_json(payload))
97
132
 
98
133
  def step(self, user_input: str) -> Decision:
134
+ """Evaluate and commit one user input against authoritative state.
135
+
136
+ Non-directive input returns ``no_directive`` without changing state.
137
+ Invalid directives return ``error`` without changing state. Accepted
138
+ directives return ``update`` and commit the resulting authoritative
139
+ state before the decision is returned.
140
+ """
141
+
142
+ evaluated = self._evaluate_transition(self._state, user_input)
143
+ self._replace_state(evaluated.next_state)
144
+ return evaluated.decision
145
+
146
+ def _evaluate_transition(self, state: State, user_input: str) -> _EvaluatedTransition:
99
147
  action = _parse_directive(user_input)
100
148
  if action is None:
101
- return _PASSTHROUGH.copy()
149
+ return _EvaluatedTransition(decision=_NO_DIRECTIVE.copy(), next_state=deepcopy(state))
102
150
 
103
- clarify_decision = self._pre_mutation_clarify(action)
104
- if clarify_decision is not None:
105
- return clarify_decision
151
+ error_decision = self._pre_mutation_error(action, state=state)
152
+ if error_decision is not None:
153
+ return _EvaluatedTransition(decision=error_decision, next_state=deepcopy(state))
106
154
 
107
- return self._apply_action(action)
155
+ next_state = self._apply_action(action, state=state)
156
+ return _EvaluatedTransition(decision=_update_decision(next_state), next_state=next_state)
108
157
 
109
158
  def _replace_state(self, state: State) -> None:
110
159
  self._state = state
111
160
 
112
- def _pre_mutation_clarify(self, action: Action) -> Decision | None:
113
- # Single clarify path: all clarify outcomes are detected before any mutation.
161
+ def _pre_mutation_error(self, action: Action, *, state: State | None = None) -> Decision | None:
162
+ candidate_state = self._state if state is None else state
163
+ # Single error path: all error outcomes are detected before any mutation.
114
164
  if action.kind in {"set_premise", "change_premise"}:
115
165
  assert action.value is not None
116
166
  if _sanitize_premise_value(action.value) == "":
117
167
  if action.kind == "set_premise":
118
- return _clarify(
168
+ return _error(
119
169
  "Premise value cannot be empty.\n"
120
170
  "Use 'set premise <value>' with a non-empty value."
121
171
  )
122
- return _clarify(
172
+ return _error(
123
173
  "Premise value cannot be empty.\n"
124
174
  "Use 'change premise to <value>' with a non-empty value."
125
175
  )
@@ -127,7 +177,7 @@ class Engine:
127
177
  if action.kind == "remove_policy_item":
128
178
  assert action.item is not None
129
179
  if _normalize_item(action.item) == "":
130
- return _clarify(
180
+ return _error(
131
181
  "Policy item cannot be empty.\n"
132
182
  "Use 'remove policy <item>' with a non-empty value."
133
183
  )
@@ -135,36 +185,36 @@ class Engine:
135
185
  if action.kind == "use_item":
136
186
  assert action.item is not None
137
187
  if _normalize_item(action.item) == "":
138
- return _clarify(
188
+ return _error(
139
189
  "Policy item cannot be empty.\nUse 'use <item>' with a non-empty value."
140
190
  )
141
191
 
142
192
  if action.kind == "prohibit_item":
143
193
  assert action.item is not None
144
194
  if _normalize_item(action.item) == "":
145
- return _clarify(
195
+ return _error(
146
196
  "Policy item cannot be empty.\nUse 'prohibit <item>' with a non-empty value."
147
197
  )
148
198
 
149
- if action.kind == "set_premise" and self._state[STATE_PREMISE] is not None:
150
- return _clarify("Premise already set.\nUse 'change premise to <value>' to modify it.")
199
+ if action.kind == "set_premise" and candidate_state[STATE_PREMISE] is not None:
200
+ return _error("Premise already set.\nUse 'change premise to <value>' to modify it.")
151
201
 
152
- if action.kind == "change_premise" and self._state[STATE_PREMISE] is None:
153
- return _clarify("No premise is set.\nUse 'set premise <value>' to define one.")
202
+ if action.kind == "change_premise" and candidate_state[STATE_PREMISE] is None:
203
+ return _error("No premise is set.\nUse 'set premise <value>' to define one.")
154
204
 
155
205
  if action.kind == "use_item":
156
206
  assert action.item is not None
157
207
  item_key = _normalize_item(action.item)
158
- if self._state[STATE_POLICIES].get(item_key) == POLICY_PROHIBIT:
159
- return _clarify(
208
+ if candidate_state[STATE_POLICIES].get(item_key) == POLICY_PROHIBIT:
209
+ return _error(
160
210
  f'"{item_key}" is currently prohibited.\nRemove or replace it before using it.'
161
211
  )
162
212
 
163
213
  if action.kind == "prohibit_item":
164
214
  assert action.item is not None
165
215
  item_key = _normalize_item(action.item)
166
- if self._state[STATE_POLICIES].get(item_key) == POLICY_USE:
167
- return _clarify(
216
+ if candidate_state[STATE_POLICIES].get(item_key) == POLICY_USE:
217
+ return _error(
168
218
  f'"{item_key}" is currently in use.\n'
169
219
  "Remove or replace it before prohibiting it."
170
220
  )
@@ -177,91 +227,89 @@ class Engine:
177
227
  if new_key == old_key:
178
228
  return None
179
229
 
180
- old_state = self._state[STATE_POLICIES].get(old_key)
181
- new_state = self._state[STATE_POLICIES].get(new_key)
230
+ old_state = candidate_state[STATE_POLICIES].get(old_key)
231
+ new_state = candidate_state[STATE_POLICIES].get(new_key)
182
232
  if old_state == POLICY_PROHIBIT:
183
- return _clarify(
233
+ return _error(
184
234
  f'"{action.old_item}" is currently prohibited.\n'
185
235
  "Submit explicit directive(s) to remove it or use a different item."
186
236
  )
187
237
  if new_state == POLICY_PROHIBIT:
188
- return _clarify(
238
+ return _error(
189
239
  f'"{action.new_item}" is currently prohibited.\n'
190
240
  "Submit explicit directive(s) to remove it or use a different item."
191
241
  )
192
242
  if old_state not in {None, POLICY_USE}:
193
- return _clarify(
243
+ return _error(
194
244
  f'"{action.old_item}" is not currently in use.\n'
195
245
  "Replacement requires an active 'use' policy."
196
246
  )
197
247
 
198
248
  return None
199
249
 
200
- def _apply_action(self, action: Action) -> Decision:
250
+ def _apply_action(self, action: Action, *, state: State) -> State:
251
+ next_state = deepcopy(state)
201
252
  kind = action.kind
202
253
 
203
254
  if kind == "set_premise":
204
255
  assert action.value is not None
205
- self._state[STATE_PREMISE] = _sanitize_premise_value(action.value)
206
- return _update_decision(self._state)
256
+ next_state[STATE_PREMISE] = _sanitize_premise_value(action.value)
257
+ return next_state
207
258
 
208
259
  if kind == "change_premise":
209
260
  assert action.value is not None
210
- self._state[STATE_PREMISE] = _sanitize_premise_value(action.value)
211
- return _update_decision(self._state)
261
+ next_state[STATE_PREMISE] = _sanitize_premise_value(action.value)
262
+ return next_state
212
263
 
213
264
  if kind == "use_item":
214
265
  assert action.item is not None
215
266
  item_key = _normalize_item(action.item)
216
267
  # Idempotent directives are updates even if state does not change.
217
- self._state[STATE_POLICIES][item_key] = POLICY_USE
218
- return _update_decision(self._state)
268
+ next_state[STATE_POLICIES][item_key] = POLICY_USE
269
+ return next_state
219
270
 
220
271
  if kind == "prohibit_item":
221
272
  assert action.item is not None
222
273
  item_key = _normalize_item(action.item)
223
274
  # Idempotent directives are updates even if state does not change.
224
- self._state[STATE_POLICIES][item_key] = POLICY_PROHIBIT
225
- return _update_decision(self._state)
275
+ next_state[STATE_POLICIES][item_key] = POLICY_PROHIBIT
276
+ return next_state
226
277
 
227
278
  if kind == "replace_use":
228
279
  assert action.new_item is not None
229
280
  assert action.old_item is not None
230
- self._apply_replacement_explicit(action.new_item, action.old_item)
231
- return _update_decision(self._state)
281
+ self._apply_replacement_explicit(next_state, action.new_item, action.old_item)
282
+ return next_state
232
283
 
233
284
  if kind == "remove_policy_item":
234
285
  assert action.item is not None
235
286
  item_key = _normalize_item(action.item)
236
- self._state[STATE_POLICIES].pop(item_key, None)
237
- return _update_decision(self._state)
287
+ next_state[STATE_POLICIES].pop(item_key, None)
288
+ return next_state
238
289
 
239
290
  if kind == "clear_premise":
240
- self._state[STATE_PREMISE] = None
241
- return _update_decision(self._state)
291
+ next_state[STATE_PREMISE] = None
292
+ return next_state
242
293
 
243
294
  if kind == "reset_policies":
244
- self._state[STATE_POLICIES] = {}
245
- return _update_decision(self._state)
295
+ next_state[STATE_POLICIES] = {}
296
+ return next_state
246
297
 
247
- self._state = _initial_state()
248
- return _update_decision(self._state)
298
+ return _initial_state()
249
299
 
250
- def _apply_replacement_explicit(self, new_item: str, old_item: str) -> None:
300
+ def _apply_replacement_explicit(self, state: State, new_item: str, old_item: str) -> None:
251
301
  new_key = _normalize_item(new_item)
252
302
  old_key = _normalize_item(old_item)
253
303
 
254
304
  if new_key == old_key:
255
305
  return
256
306
 
257
- self._state[STATE_POLICIES].pop(old_key, None)
258
- self._state[STATE_POLICIES][new_key] = POLICY_USE
307
+ state[STATE_POLICIES].pop(old_key, None)
308
+ state[STATE_POLICIES][new_key] = POLICY_USE
259
309
 
260
310
 
261
311
  def _parse_directive(user_input: str) -> Action | None:
262
- # Engine semantics intentionally depend on grammar's internal parsed form.
263
- # This keeps syntax authority in grammar without making the parser a public API.
264
- parsed = _parse_canonical_directive(user_input)
312
+ parsed = decompose_directive(user_input)
265
313
  if parsed is None:
266
314
  return None
267
315
 
@@ -357,17 +405,9 @@ def _normalize_item(value: str) -> str:
357
405
  return normalized.strip()
358
406
 
359
407
 
360
- def _clarify(prompt: str) -> Decision:
361
- return {
362
- "kind": DecisionKind.CLARIFY,
363
- "state": None,
364
- "prompt_to_user": prompt,
365
- }
408
+ def _error(message: str) -> ErrorDecision:
409
+ return {"kind": DECISION_ERROR, "message": message}
366
410
 
367
411
 
368
- def _update_decision(state: State) -> Decision:
369
- return {
370
- "kind": DecisionKind.UPDATE,
371
- "state": deepcopy(state),
372
- "prompt_to_user": None,
373
- }
412
+ def _update_decision(state: State) -> UpdateDecision:
413
+ return {"kind": DECISION_UPDATE, "state": deepcopy(state)}
@@ -8,6 +8,8 @@ from types import MappingProxyType
8
8
 
9
9
 
10
10
  class DirectiveKind(StrEnum):
11
+ """Enumerate the supported canonical directive families."""
12
+
11
13
  SET_PREMISE = "set_premise"
12
14
  CHANGE_PREMISE = "change_premise"
13
15
  USE_ITEM = "use_item"
@@ -21,12 +23,16 @@ class DirectiveKind(StrEnum):
21
23
 
22
24
  @dataclass(frozen=True, slots=True)
23
25
  class ValidatedDirective:
26
+ """Classify text as one canonical directive kind without exposing operands."""
27
+
24
28
  text: str
25
29
  kind: DirectiveKind
26
30
 
27
31
 
28
32
  @dataclass(frozen=True, slots=True)
29
- class _GrammarParsedDirective:
33
+ class CanonicalDirective:
34
+ """Represent one parsed canonical directive and its named operands."""
35
+
30
36
  text: str
31
37
  kind: DirectiveKind
32
38
  operands: MappingProxyType[str, str]
@@ -173,7 +179,14 @@ def _operand_starts_with_token(value: str, token: str) -> bool:
173
179
  return normalized == token or normalized.startswith(f"{token} ")
174
180
 
175
181
 
176
- def _match_canonical_directive_start(text: str, start: int) -> int | None:
182
+ def match_canonical_directive_start(text: str, start: int) -> int | None:
183
+ """Locate a canonical directive prefix at a given character position.
184
+
185
+ This classifies whether canonical directive syntax begins at ``start`` and,
186
+ when it does, returns the index immediately after the directive keyword
187
+ prefix. It does not parse operands, validate the full directive payload, or
188
+ evaluate multi-directive state semantics.
189
+ """
177
190
  if start < 0 or start >= len(text):
178
191
  return None
179
192
 
@@ -230,20 +243,27 @@ def _match_directive_token(
230
243
  return index
231
244
 
232
245
 
233
- def _contains_multiple_canonical_directives(text: str) -> bool:
234
- first_start = _match_canonical_directive_start(text, 0)
246
+ def contains_multiple_canonical_directives(text: str) -> bool:
247
+ """Report whether text contains more than one canonical directive start.
248
+
249
+ This detects compound directive text by looking for multiple canonical
250
+ directive prefixes in the same input. It does not parse directive operands,
251
+ repair malformed text, or determine whether a whole string should be
252
+ accepted as a single directive.
253
+ """
254
+ first_start = match_canonical_directive_start(text, 0)
235
255
  if first_start is None:
236
256
  return False
237
257
 
238
258
  for index in range(first_start, len(text)):
239
- next_start = _match_canonical_directive_start(text, index)
259
+ next_start = match_canonical_directive_start(text, index)
240
260
  if next_start is not None:
241
261
  return True
242
262
 
243
263
  return False
244
264
 
245
265
 
246
- def _parse_replace_use(trimmed_text: str) -> _GrammarParsedDirective | None:
266
+ def _parse_replace_use(trimmed_text: str) -> CanonicalDirective | None:
247
267
  match = _REPLACE_RE.fullmatch(trimmed_text)
248
268
  if match is None:
249
269
  return None
@@ -258,34 +278,41 @@ def _parse_replace_use(trimmed_text: str) -> _GrammarParsedDirective | None:
258
278
  normalized_payload = _normalized_for_matching(trimmed_text)
259
279
  if normalized_payload.count(_INSTEAD_OF_DELIMITER) != 1:
260
280
  return None
261
- return _GrammarParsedDirective(
281
+ return CanonicalDirective(
262
282
  text=trimmed_text,
263
283
  kind=DirectiveKind.REPLACE_USE,
264
284
  operands=MappingProxyType({"new_item": new_item, "old_item": old_item}),
265
285
  )
266
286
 
267
287
 
268
- def _parse_directive(text: str) -> _GrammarParsedDirective | None:
288
+ def decompose_directive(text: str) -> CanonicalDirective | None:
289
+ """Parse one canonical directive into its semantic kind and operands.
290
+
291
+ This determines whether ``text`` is a single canonical directive and, when
292
+ it is, returns the directive kind plus canonical operand names with the
293
+ original operand text preserved. It does not repair input, infer intent, or
294
+ evaluate directive effects against compiler state.
295
+ """
269
296
  trimmed_text = _trim_ascii_whitespace(text)
270
297
  if trimmed_text == "":
271
298
  return None
272
- if _contains_multiple_canonical_directives(trimmed_text):
299
+ if contains_multiple_canonical_directives(trimmed_text):
273
300
  return None
274
301
 
275
302
  normalized = _normalized_for_matching(trimmed_text)
276
303
 
277
304
  if normalized == "clear premise":
278
- return _GrammarParsedDirective(
305
+ return CanonicalDirective(
279
306
  text=text, kind=DirectiveKind.CLEAR_PREMISE, operands=MappingProxyType({})
280
307
  )
281
308
  if normalized == "reset policies":
282
- return _GrammarParsedDirective(
309
+ return CanonicalDirective(
283
310
  text=text,
284
311
  kind=DirectiveKind.RESET_POLICIES,
285
312
  operands=MappingProxyType({}),
286
313
  )
287
314
  if normalized == "clear state":
288
- return _GrammarParsedDirective(
315
+ return CanonicalDirective(
289
316
  text=text, kind=DirectiveKind.CLEAR_STATE, operands=MappingProxyType({})
290
317
  )
291
318
 
@@ -296,7 +323,7 @@ def _parse_directive(text: str) -> _GrammarParsedDirective | None:
296
323
  value = match.group("value")
297
324
  if not _operand_has_content(value) or _operand_starts_with_token(value, "to"):
298
325
  return None
299
- return _GrammarParsedDirective(
326
+ return CanonicalDirective(
300
327
  text=text,
301
328
  kind=DirectiveKind.SET_PREMISE,
302
329
  operands=MappingProxyType({"value": value}),
@@ -309,7 +336,7 @@ def _parse_directive(text: str) -> _GrammarParsedDirective | None:
309
336
  value = match.group("value")
310
337
  if not _operand_has_content(value):
311
338
  return None
312
- return _GrammarParsedDirective(
339
+ return CanonicalDirective(
313
340
  text=text,
314
341
  kind=DirectiveKind.CHANGE_PREMISE,
315
342
  operands=MappingProxyType({"value": value}),
@@ -332,7 +359,7 @@ def _parse_directive(text: str) -> _GrammarParsedDirective | None:
332
359
  or _INSTEAD_OF_DELIMITER in normalized_item
333
360
  ):
334
361
  return None
335
- return _GrammarParsedDirective(
362
+ return CanonicalDirective(
336
363
  text=text,
337
364
  kind=DirectiveKind.USE_ITEM,
338
365
  operands=MappingProxyType({"item": item}),
@@ -345,7 +372,7 @@ def _parse_directive(text: str) -> _GrammarParsedDirective | None:
345
372
  item = match.group("item")
346
373
  if not _operand_has_content(item):
347
374
  return None
348
- return _GrammarParsedDirective(
375
+ return CanonicalDirective(
349
376
  text=text,
350
377
  kind=DirectiveKind.PROHIBIT_ITEM,
351
378
  operands=MappingProxyType({"item": item}),
@@ -358,7 +385,7 @@ def _parse_directive(text: str) -> _GrammarParsedDirective | None:
358
385
  item = match.group("item")
359
386
  if not _operand_has_content(item):
360
387
  return None
361
- return _GrammarParsedDirective(
388
+ return CanonicalDirective(
362
389
  text=text,
363
390
  kind=DirectiveKind.REMOVE_POLICY,
364
391
  operands=MappingProxyType({"item": item}),
@@ -368,29 +395,37 @@ def _parse_directive(text: str) -> _GrammarParsedDirective | None:
368
395
 
369
396
 
370
397
  def validate_directive(text: str) -> ValidatedDirective | None:
371
- parsed = _parse_directive(text)
398
+ """Classify whether text is a canonical directive.
399
+
400
+ This determines whether ``text`` belongs to one canonical directive family
401
+ and returns only the normalized semantic kind needed for classification. It
402
+ does not expose operands, render directives, repair malformed text, or
403
+ evaluate any state transition.
404
+ """
405
+ parsed = decompose_directive(text)
372
406
  if parsed is None:
373
407
  return None
374
408
  return ValidatedDirective(text=parsed.text, kind=parsed.kind)
375
409
 
376
410
 
377
- def _parse_canonical_directive(text: str) -> _GrammarParsedDirective | None:
378
- return _parse_directive(text)
379
-
380
-
381
- def match_canonical_directive_start(text: str, start: int) -> int | None:
382
- return _match_canonical_directive_start(text, start)
383
-
384
-
385
- def contains_multiple_canonical_directives(text: str) -> bool:
386
- return _contains_multiple_canonical_directives(text)
387
-
388
-
389
411
  def is_canonical_directive(text: str) -> bool:
412
+ """Return whether text is exactly one canonical directive.
413
+
414
+ This provides boolean directive classification for callers that only need a
415
+ yes-or-no answer. It does not parse operands, explain rejection reasons, or
416
+ perform validation beyond canonical grammar recognition.
417
+ """
390
418
  return validate_directive(text) is not None
391
419
 
392
420
 
393
421
  def render_directive(kind: DirectiveKind, /, **operands: str) -> str:
422
+ """Render canonical directive text from a semantic kind and operands.
423
+
424
+ This determines the exact canonical spelling for an existing grammar
425
+ capability and rejects operand combinations that would not round-trip as the
426
+ requested directive kind. It does not infer missing operands, parse user
427
+ input, or extend the grammar with new behaviors.
428
+ """
394
429
  try:
395
430
  normalized_kind = kind if isinstance(kind, DirectiveKind) else DirectiveKind(kind)
396
431
  spec = _DIRECTIVE_SPECS[normalized_kind]
@@ -426,9 +461,13 @@ def render_directive(kind: DirectiveKind, /, **operands: str) -> str:
426
461
 
427
462
 
428
463
  __all__ = [
464
+ "CanonicalDirective",
429
465
  "DirectiveKind",
430
466
  "ValidatedDirective",
467
+ "contains_multiple_canonical_directives",
468
+ "decompose_directive",
431
469
  "is_canonical_directive",
470
+ "match_canonical_directive_start",
432
471
  "render_directive",
433
472
  "validate_directive",
434
473
  ]
context_compiler/repl.py CHANGED
@@ -1,9 +1,11 @@
1
+ """Command-line and REPL entry points for interacting with the engine."""
2
+
1
3
  import json
2
4
  import sys
3
5
  from typing import TextIO
4
6
 
5
7
  from . import __version__, create_engine
6
- from .const import DECISION_CLARIFY, DECISION_PASSTHROUGH
8
+ from .const import DECISION_ERROR
7
9
  from .controller import (
8
10
  OUTPUT_VERSION,
9
11
  PreviewResult,
@@ -14,7 +16,8 @@ from .controller import (
14
16
  )
15
17
  from .controller import preview as controller_preview
16
18
  from .controller import step as controller_step
17
- from .engine import Decision, DecisionKind, Engine, State
19
+ from .decision_helpers import is_error, is_no_directive, is_update
20
+ from .engine import Decision, Engine, State
18
21
 
19
22
  _EXIT_TOKENS = {"exit", "quit"}
20
23
  _HELP_TOKENS = {"help", "?"}
@@ -44,11 +47,7 @@ def _has_embedded_newline(raw_line: str) -> bool:
44
47
 
45
48
 
46
49
  def _multi_command_decision() -> Decision:
47
- return {
48
- "kind": DecisionKind.CLARIFY,
49
- "state": None,
50
- "prompt_to_user": _MULTI_COMMAND_PROMPT,
51
- }
50
+ return {"kind": DECISION_ERROR, "message": _MULTI_COMMAND_PROMPT}
52
51
 
53
52
 
54
53
  def _print_interactive_help(out_stream: TextIO) -> None:
@@ -69,7 +68,7 @@ def _print_interactive_help(out_stream: TextIO) -> None:
69
68
  print(" clear state", file=out_stream)
70
69
  print("Bare input behavior remains unchanged.", file=out_stream)
71
70
  print("preview is a deterministic dry-run and never mutates live state.", file=out_stream)
72
- print("clarify results are immediate messages and do not reserve later input.", file=out_stream)
71
+ print("error results are immediate messages and do not reserve later input.", file=out_stream)
73
72
 
74
73
 
75
74
  def _render_state_lines(state: State) -> list[str]:
@@ -89,16 +88,15 @@ def _render_state_lines(state: State) -> list[str]:
89
88
 
90
89
 
91
90
  def _render_decision_lines(decision: Decision) -> list[str]:
92
- kind = decision["kind"]
93
- if kind == DECISION_PASSTHROUGH:
94
- return ["passthrough"]
95
- if kind == DECISION_CLARIFY:
96
- prompt = decision["prompt_to_user"] or ""
97
- prompt_lines = prompt.splitlines() if prompt else [""]
91
+ if is_no_directive(decision):
92
+ return ["no_directive"]
93
+ if is_error(decision):
94
+ message = decision["message"]
95
+ prompt_lines = message.splitlines() if message else [""]
98
96
  return [f"error: {prompt_lines[0]}", *prompt_lines[1:]]
99
97
 
98
+ assert is_update(decision)
100
99
  state = decision["state"]
101
- assert state is not None
102
100
  return ["updated", *_render_state_lines(state)]
103
101
 
104
102
 
@@ -249,11 +247,19 @@ def run_repl(
249
247
  json_mode: bool = False,
250
248
  engine: Engine | None = None,
251
249
  ) -> None:
250
+ """Run the interactive or line-oriented REPL against one engine instance.
251
+
252
+ Interactive mode exposes command helpers such as ``state`` and ``preview``.
253
+ Non-interactive mode consumes one input line at a time and can optionally
254
+ emit NDJSON records. Preview requests are evaluated through the shared
255
+ controller preview path and never mutate the live engine state.
256
+ """
257
+
252
258
  active_engine = create_engine() if engine is None else engine
253
259
 
254
260
  if _is_interactive(in_stream, out_stream):
255
261
  print("Context Compiler REPL (0.5). Type help for commands.", file=out_stream)
256
- print("Non-directive input is passthrough.", file=out_stream)
262
+ print("Non-directive input is no_directive.", file=out_stream)
257
263
 
258
264
  while True:
259
265
  line = in_stream.readline()
@@ -425,6 +431,8 @@ def run_repl(
425
431
 
426
432
 
427
433
  def main() -> int: # pragma: no cover
434
+ """Run the command-line entry point and return the process exit status."""
435
+
428
436
  args = sys.argv[1:]
429
437
  if not args:
430
438
  run_repl(sys.stdin, sys.stdout)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: context-compiler
3
- Version: 0.9.0.dev2
3
+ Version: 0.9.0.dev4
4
4
  Summary: Deterministic conversational state engine for LLM applications.
5
5
  Project-URL: Homepage, https://github.com/rlippmann/context-compiler
6
6
  Project-URL: Repository, https://github.com/rlippmann/context-compiler
@@ -36,7 +36,7 @@ Description-Content-Type: text/markdown
36
36
 
37
37
  Context Compiler is a deterministic conversational state authority for LLM applications.
38
38
  It handles canonical directive execution, semantic validation, deterministic
39
- clarify decisions, runtime semantic continuation boundaries, and structured authoritative state for
39
+ error decisions, runtime semantic continuation boundaries, and structured authoritative state for
40
40
  the host.
41
41
 
42
42
  ## What Context Compiler provides
@@ -44,7 +44,7 @@ the host.
44
44
  Context Compiler gives hosts fixed state rules:
45
45
 
46
46
  - handle canonical explicit state changes with deterministic rules
47
- - clarification instead of silent overwrite for blocked/ambiguous changes
47
+ - error instead of silent overwrite for blocked/ambiguous changes
48
48
  - preserve supported pending continuation when explicit confirmation is required
49
49
  - export and import authoritative state for host-managed persistence
50
50
  - produce structured authoritative state for downstream host decisions
@@ -89,7 +89,7 @@ Context Compiler makes state-change rules explicit so behavior stays repeatable.
89
89
  The architecture has three layers:
90
90
 
91
91
  - syntax classification decides whether input is a canonical directive, invalid
92
- directive-shaped syntax, or ordinary passthrough
92
+ directive-shaped syntax, or ordinary no_directive
93
93
  - semantic evaluation decides whether a canonical directive updates state,
94
94
  clarifies, or no-ops
95
95
  - semantic continuation optionally preserves a deterministic blocked transition
@@ -123,7 +123,7 @@ use podman instead of docker
123
123
  - Without explicit state transition rules: behavior depends on host/model handling
124
124
  - Context Compiler: applies the deterministic resulting transition when
125
125
  `docker` is absent and `use podman` is otherwise valid; other semantic
126
- conflicts may still clarify
126
+ conflicts may still error
127
127
 
128
128
  ### Lifecycle enforcement
129
129
 
@@ -133,7 +133,7 @@ change premise to formal tone
133
133
  ```
134
134
 
135
135
  - Without explicit transition checks: behavior depends on host/model handling
136
- - Context Compiler: asks for clarification and keeps saved state unchanged
136
+ - Context Compiler: asks for error and keeps saved state unchanged
137
137
 
138
138
  ---
139
139
 
@@ -150,9 +150,9 @@ Decision
150
150
  │
151
151
  ▼
152
152
  Host Application
153
- ├─ clarify → ask user
154
- ├─ passthrough → call LLM
155
- └─ update → authoritative state mutated; host may call LLM with compiled state
153
+ ├─ error → ask user
154
+ ├─ no_directive → no canonical directive recognized; host decides what to do next
155
+ └─ update → authoritative state mutated; host may use compiled state downstream
156
156
  ```
157
157
 
158
158
  The compiler never calls the LLM. Your app decides what to do with the returned
@@ -167,8 +167,7 @@ Use Context Compiler in your host application first:
167
167
  ```python
168
168
  from context_compiler import (
169
169
  create_engine,
170
- get_clarify_prompt,
171
- is_clarify,
170
+ is_error,
172
171
  is_update,
173
172
  )
174
173
 
@@ -177,8 +176,8 @@ engine = create_engine()
177
176
  user_input = "set premise current project uses uv"
178
177
  decision = engine.step(user_input)
179
178
 
180
- if is_clarify(decision):
181
- show_to_user(get_clarify_prompt(decision))
179
+ if is_error(decision):
180
+ show_to_user(decision["message"])
182
181
  elif is_update(decision):
183
182
  messages = build_messages(engine.state, user_input)
184
183
  render(call_llm(messages))
@@ -277,22 +276,30 @@ uv run pytest
277
276
  Each user message produces a `Decision`.
278
277
 
279
278
  ```python
280
- class Decision(TypedDict):
281
- kind: Literal["passthrough", "update", "clarify"]
282
- state: dict | None
283
- prompt_to_user: str | None
279
+ class NoDirectiveDecision(TypedDict):
280
+ kind: Literal["no_directive"]
281
+
282
+ class UpdateDecision(TypedDict):
283
+ kind: Literal["update"]
284
+ state: State
285
+
286
+ class ErrorDecision(TypedDict):
287
+ kind: Literal["error"]
288
+ message: str
289
+
290
+ Decision = NoDirectiveDecision | UpdateDecision | ErrorDecision
284
291
  ```
285
292
 
286
293
  Meaning:
287
294
 
288
295
  | kind | host behavior |
289
296
  | --- | --- |
290
- | passthrough | forward user input to LLM |
291
- | update | authoritative state mutated; host may call LLM with updated state |
292
- | clarify | show `prompt_to_user` and do not call the LLM |
297
+ | no_directive | no canonical directive recognized; no authoritative state change; host decides what to do next |
298
+ | update | authoritative state mutated; host may use updated state downstream |
299
+ | error | show `message` and do not continue normal downstream processing yet |
293
300
 
294
- For normal app code, prefer the exported decision helpers (`is_clarify`,
295
- `is_update`, `is_passthrough`, `get_clarify_prompt`, `get_decision_state`)
301
+ For normal app code, prefer the exported decision helpers (`is_error`,
302
+ `is_update`, `is_no_directive`, `get_error_message`, `get_decision_state`)
296
303
  instead of direct key traversal.
297
304
 
298
305
  See [docs/api-reference.md](docs/api-reference.md) for the full public API
@@ -303,8 +310,8 @@ Common API entry points:
303
310
  - engine lifecycle: `create_engine(...)`, `engine.step(...)`, `engine.state`,
304
311
  `engine.premise`, `engine.policies`, `engine.export_json(...)`,
305
312
  `engine.import_json(...)`
306
- - decision helpers: `is_clarify(...)`, `is_update(...)`, `is_passthrough(...)`,
307
- `get_clarify_prompt(...)`, `get_decision_state(...)`
313
+ - decision helpers: `is_error(...)`, `is_update(...)`, `is_no_directive(...)`,
314
+ `get_error_message(...)`, `get_decision_state(...)`
308
315
  - state transport: `engine.export_json(...)`, `engine.import_json(...)`
309
316
  - controller API: `step(...)`
310
317
  - audit APIs: `preview(...)`, `state_diff(...)`
@@ -441,14 +448,14 @@ User: clear state
441
448
 
442
449
  Grammar invariant: one input may contain at most one canonical directive.
443
450
  Directive-shaped invalid input is outside the canonical language, and
444
- `clarify` is reserved for canonical directives that later fail semantic
451
+ `error` is reserved for canonical directives that later fail semantic
445
452
  evaluation against authoritative state.
446
453
 
447
454
  Pending continuation is a separate runtime layer. It may exist only after a
448
- canonical directive reaches a supported semantic `clarify` case. It never
455
+ canonical directive reaches a supported semantic `error` case. It never
449
456
  repairs malformed syntax or reinterprets non-canonical input as a directive.
450
457
  An absent source item in a canonical replacement directive is not, by itself,
451
- such a `clarify` case.
458
+ such a `error` case.
452
459
 
453
460
  Examples:
454
461
 
@@ -496,7 +503,7 @@ boundary, see [DirectiveGrammarSpec.md](docs/DirectiveGrammarSpec.md).
496
503
  **Isn't this just prompt reinjection?**
497
504
  No. Prompt construction is one downstream use of authoritative state.
498
505
  Context Compiler is the authority layer that decides when state changes are
499
- allowed, when clarification is required, and how continuation state is
506
+ allowed, when error is required, and how continuation state is
500
507
  restored. For runnable application-layer examples, see
501
508
  [`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
502
509
 
@@ -519,7 +526,7 @@ User: prohibit python_script
519
526
 
520
527
  Without an authority layer, the application must invent conflict-resolution and
521
528
  continuation rules itself. Context Compiler applies deterministic
522
- state-transition rules and can return clarification instead of silently
529
+ state-transition rules and can return error instead of silently
523
530
  overwriting state.
524
531
 
525
532
  ---
@@ -531,7 +538,7 @@ overwriting state.
531
538
  - State changes only through explicit user directives or confirmation.
532
539
  - Identical input sequences produce identical compiler state.
533
540
  - Model responses never modify compiler state.
534
- - Ambiguous directives trigger clarification instead of changing state.
541
+ - Ambiguous directives trigger error instead of changing state.
535
542
  - Syntax errors never create pending continuation.
536
543
 
537
544
  Behavioral tests and Hypothesis-based property tests verify these invariants.
@@ -0,0 +1,14 @@
1
+ context_compiler/__init__.py,sha256=acFX-oUeMASVlReQrzmHIL04dy06kdeKaL8yEfE4RwY,1067
2
+ context_compiler/audit.py,sha256=AhDYy1G-xc4Ov9Jnjf_k11-hqWEp5BVIFztBJWbMpBM,476
3
+ context_compiler/const.py,sha256=WeEGDXY8lepFghH9wko0hH51oxoZQBfDnFxSyeIaaWI,465
4
+ context_compiler/controller.py,sha256=Op1-RTKltIhSDLwfhohmH3k7x4q5XdeE7ZtKPJIzqZo,5229
5
+ context_compiler/decision_helpers.py,sha256=1UFg3E3epSjuH_5ErXGigTTacKCs_IgTYV3kxBvdV-4,1302
6
+ context_compiler/engine.py,sha256=igGomL7NTELgdBCB1Z0LpD2H2nA2DC-7BY4-Rb-PxPU,14721
7
+ context_compiler/grammar.py,sha256=vWOK7Hp5_-X_2XfkhosPM4MlIyGZZ6EV3DtGsZUv-8A,16182
8
+ context_compiler/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ context_compiler/repl.py,sha256=OWEfdK8q0dmI3QXdYwxy_pl5gfxwNesqQgkPx3ne62g,17018
10
+ context_compiler-0.9.0.dev4.dist-info/METADATA,sha256=-XLo94NJg0qCC0PalXt92wfbhwPPH1lgPs06bjsHKk0,18346
11
+ context_compiler-0.9.0.dev4.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
12
+ context_compiler-0.9.0.dev4.dist-info/entry_points.txt,sha256=xnk4WE9zaC2NAMIF59WD3Y_45E8aA9KH-pZ4lqSzH-U,64
13
+ context_compiler-0.9.0.dev4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
14
+ context_compiler-0.9.0.dev4.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- context_compiler/__init__.py,sha256=2nVa-dMpdcT_7BfP6dTBoLWanBItTWBBQ--AXEgNA7I,977
2
- context_compiler/audit.py,sha256=AhDYy1G-xc4Ov9Jnjf_k11-hqWEp5BVIFztBJWbMpBM,476
3
- context_compiler/const.py,sha256=DgqVhkaNQ3hx2tToqHi9ZA6X6EYZj618vXacKxU-TDM,467
4
- context_compiler/controller.py,sha256=U9n9MVjbrIqlrcpqMkDTwvt_WC-FzOE2CZqbwcOXjQg,3858
5
- context_compiler/decision_helpers.py,sha256=RDl7TYeM7QywEFpWd3d4vw6fRnSfzgWXtgFFv044pe4,659
6
- context_compiler/engine.py,sha256=june_5yvUF8oyWJQzoL3SINqwoY8BG-MPbjgqETwSMc,13004
7
- context_compiler/grammar.py,sha256=PGBS5dZHx9GLHd-Emj-9TAna5PlN8MkaBMcr4HsVGLM,14165
8
- context_compiler/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- context_compiler/repl.py,sha256=ZjjiKSj88KHKYJiaZ_YOkCncsOY8RYrHtvXUkdYVkgs,16549
10
- context_compiler-0.9.0.dev2.dist-info/METADATA,sha256=Z2kBw2FJGEV2T9IOf3Va-JQv4Rf1nYdAfhnAp2qvnGY,18132
11
- context_compiler-0.9.0.dev2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
12
- context_compiler-0.9.0.dev2.dist-info/entry_points.txt,sha256=xnk4WE9zaC2NAMIF59WD3Y_45E8aA9KH-pZ4lqSzH-U,64
13
- context_compiler-0.9.0.dev2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
14
- context_compiler-0.9.0.dev2.dist-info/RECORD,,