context-compiler 0.9.0.dev10__tar.gz → 0.9.0.dev12__tar.gz
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.
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/PKG-INFO +1 -1
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/pyproject.toml +1 -1
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/src/context_compiler/engine.py +11 -2
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/src/context_compiler/grammar.py +98 -48
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/.gitignore +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/LICENSE +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/README.md +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/01_llm_contradiction_error.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/02_llm_constraint_guardrail.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/03_llm_premise_guardrail.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/04_llm_tool_denylist_guardrail.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/05_llm_prompt_drift_vs_state.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/06_llm_context_compaction.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/07_llm_prompt_vs_state.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/08_llm_replacement_precondition.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/09_llm_confirmation_no_directive.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/README.md +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/__init__.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/common.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/llm_client.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/run_demo.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/01_persistent_guardrails.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/02_configuration_and_correction.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/03_ambiguity_with_error.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/04_tool_governance_denylist.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/05_llm_integration_pattern.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/06_step_sequence_and_state_restore.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/07_single_policy_correction.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/README.md +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/_util.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/host_support/__init__.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/host_support/provider_mode.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/src/context_compiler/__init__.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/src/context_compiler/const.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/src/context_compiler/decision_helpers.py +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/src/context_compiler/py.typed +0 -0
- {context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/src/context_compiler/repl.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: context-compiler
|
|
3
|
-
Version: 0.9.0.
|
|
3
|
+
Version: 0.9.0.dev12
|
|
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
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/src/context_compiler/engine.py
RENAMED
|
@@ -276,7 +276,12 @@ def _load_state_obj(raw: object) -> _State:
|
|
|
276
276
|
if not isinstance(policies, dict):
|
|
277
277
|
raise ValueError("Invalid state payload.")
|
|
278
278
|
|
|
279
|
+
sanitized_premise = None if premise is None else _sanitize_premise_value(premise)
|
|
280
|
+
if premise is not None and sanitized_premise == "":
|
|
281
|
+
raise ValueError("Invalid state payload.")
|
|
282
|
+
|
|
279
283
|
normalized_policies: dict[str, PolicyValue] = {}
|
|
284
|
+
seen_policy_sources: dict[str, str] = {}
|
|
280
285
|
for key, value in policies.items():
|
|
281
286
|
if not isinstance(key, str):
|
|
282
287
|
raise ValueError("Invalid state payload.")
|
|
@@ -285,10 +290,14 @@ def _load_state_obj(raw: object) -> _State:
|
|
|
285
290
|
normalized_key = _normalize_item(key)
|
|
286
291
|
if normalized_key == "":
|
|
287
292
|
raise ValueError("Invalid state payload.")
|
|
293
|
+
prior_source = seen_policy_sources.get(normalized_key)
|
|
294
|
+
if prior_source is not None and prior_source != key:
|
|
295
|
+
raise ValueError("Invalid state payload.")
|
|
296
|
+
seen_policy_sources[normalized_key] = key
|
|
288
297
|
normalized_policies[normalized_key] = value
|
|
289
298
|
|
|
290
299
|
return {
|
|
291
|
-
STATE_PREMISE:
|
|
300
|
+
STATE_PREMISE: sanitized_premise,
|
|
292
301
|
STATE_POLICIES: dict(sorted(normalized_policies.items())),
|
|
293
302
|
STATE_VERSION: SCHEMA_VERSION,
|
|
294
303
|
}
|
|
@@ -303,7 +312,7 @@ def _sanitize_premise_value(value: str) -> str:
|
|
|
303
312
|
def _normalize_item(value: str) -> str:
|
|
304
313
|
normalized = unicode_normalize("NFKC", value)
|
|
305
314
|
normalized = normalized.replace("’", "'").replace("`", "'")
|
|
306
|
-
normalized = normalized.
|
|
315
|
+
normalized = normalized.casefold()
|
|
307
316
|
normalized = re.sub(r"\s+", " ", normalized).strip()
|
|
308
317
|
return normalized
|
|
309
318
|
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/src/context_compiler/grammar.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Immutable canonical grammar helpers for Context Compiler directives."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from collections.abc import Callable
|
|
4
|
+
from collections.abc import Callable, Mapping
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from enum import StrEnum
|
|
7
7
|
from types import MappingProxyType
|
|
@@ -33,14 +33,24 @@ class DirectiveSyntaxFailure(StrEnum):
|
|
|
33
33
|
class CanonicalDirective:
|
|
34
34
|
"""Represent one parsed canonical directive and its named operands.
|
|
35
35
|
|
|
36
|
-
``text``
|
|
37
|
-
|
|
36
|
+
``text`` is the canonical serialized directive text derived from ``kind``
|
|
37
|
+
and ``operands``.
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
-
text: str
|
|
41
40
|
kind: DirectiveKind
|
|
42
41
|
operands: MappingProxyType[str, str]
|
|
43
42
|
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
normalized_kind = _normalize_directive_kind(self.kind)
|
|
45
|
+
normalized_operands = _normalize_canonical_operands(normalized_kind, self.operands)
|
|
46
|
+
object.__setattr__(self, "kind", normalized_kind)
|
|
47
|
+
object.__setattr__(self, "operands", MappingProxyType(normalized_operands))
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def text(self) -> str:
|
|
51
|
+
"""Return the canonical serialized directive text."""
|
|
52
|
+
return _serialize_canonical_directive(self.kind, self.operands)
|
|
53
|
+
|
|
44
54
|
|
|
45
55
|
@dataclass(frozen=True, slots=True)
|
|
46
56
|
class InvalidDirectiveSyntax:
|
|
@@ -365,7 +375,6 @@ def _parse_replace_use(trimmed_text: str) -> CanonicalDirective | None:
|
|
|
365
375
|
if normalized_payload.count(_INSTEAD_OF_DELIMITER) != 1:
|
|
366
376
|
return None
|
|
367
377
|
return CanonicalDirective(
|
|
368
|
-
text=trimmed_text,
|
|
369
378
|
kind=DirectiveKind.REPLACE_USE,
|
|
370
379
|
operands=MappingProxyType({"new_item": new_item, "old_item": old_item}),
|
|
371
380
|
)
|
|
@@ -384,6 +393,74 @@ def _invalid_directive_syntax(
|
|
|
384
393
|
)
|
|
385
394
|
|
|
386
395
|
|
|
396
|
+
def _normalize_directive_kind(kind: DirectiveKind | str) -> DirectiveKind:
|
|
397
|
+
try:
|
|
398
|
+
return kind if isinstance(kind, DirectiveKind) else DirectiveKind(kind)
|
|
399
|
+
except ValueError as exc:
|
|
400
|
+
raise ValueError(f"Unsupported directive kind: {kind!r}") from exc
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _normalize_canonical_operands(
|
|
404
|
+
kind: DirectiveKind,
|
|
405
|
+
operands: Mapping[str, str],
|
|
406
|
+
) -> dict[str, str]:
|
|
407
|
+
spec = _DIRECTIVE_SPECS[kind]
|
|
408
|
+
expected_names = set(spec.operand_names)
|
|
409
|
+
actual_names = set(operands)
|
|
410
|
+
unexpected_names = actual_names - expected_names
|
|
411
|
+
missing_names = expected_names - actual_names
|
|
412
|
+
if missing_names:
|
|
413
|
+
missing = ", ".join(sorted(missing_names))
|
|
414
|
+
raise ValueError(f"Missing required operands for {kind.value}: {missing}")
|
|
415
|
+
if unexpected_names:
|
|
416
|
+
unexpected = ", ".join(sorted(unexpected_names))
|
|
417
|
+
raise ValueError(f"Unexpected operands for {kind.value}: {unexpected}")
|
|
418
|
+
|
|
419
|
+
normalized_operands: dict[str, str] = {}
|
|
420
|
+
for name in spec.operand_names:
|
|
421
|
+
raw_value = operands[name]
|
|
422
|
+
if not isinstance(raw_value, str):
|
|
423
|
+
raise ValueError(f"Operand {name!r} for {kind.value} must be a string.")
|
|
424
|
+
if not _operand_has_content(raw_value):
|
|
425
|
+
raise ValueError(f"Operand {name!r} for {kind.value} cannot be empty.")
|
|
426
|
+
normalized_operands[name] = raw_value
|
|
427
|
+
|
|
428
|
+
_validate_operand_constraints(kind, normalized_operands)
|
|
429
|
+
_validate_rendered_canonical_shape(kind, normalized_operands)
|
|
430
|
+
return normalized_operands
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _validate_operand_constraints(kind: DirectiveKind, operands: Mapping[str, str]) -> None:
|
|
434
|
+
if kind is DirectiveKind.SET_PREMISE:
|
|
435
|
+
value = operands["value"]
|
|
436
|
+
if _operand_starts_with_token(value, "to"):
|
|
437
|
+
raise ValueError(f"Operands do not produce a canonical {kind.value} directive.")
|
|
438
|
+
return
|
|
439
|
+
|
|
440
|
+
if kind is DirectiveKind.USE_ITEM:
|
|
441
|
+
item = operands["item"]
|
|
442
|
+
normalized_item = _normalized_for_matching(item)
|
|
443
|
+
if normalized_item == "instead of" or normalized_item.startswith("instead of "):
|
|
444
|
+
raise ValueError(f"Operands do not produce a canonical {kind.value} directive.")
|
|
445
|
+
if _INSTEAD_OF_DELIMITER in normalized_item:
|
|
446
|
+
raise ValueError(f"Operands do not produce a canonical {kind.value} directive.")
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
if kind is DirectiveKind.REPLACE_USE:
|
|
450
|
+
new_item = operands["new_item"]
|
|
451
|
+
old_item = operands["old_item"]
|
|
452
|
+
if _INSTEAD_OF_DELIMITER in _normalized_for_matching(
|
|
453
|
+
new_item
|
|
454
|
+
) or _INSTEAD_OF_DELIMITER in _normalized_for_matching(old_item):
|
|
455
|
+
raise ValueError(f"Operands do not produce a canonical {kind.value} directive.")
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _validate_rendered_canonical_shape(kind: DirectiveKind, operands: Mapping[str, str]) -> None:
|
|
459
|
+
rendered = _serialize_canonical_directive(kind, MappingProxyType(dict(operands)))
|
|
460
|
+
if _contains_multiple_canonical_directives(rendered):
|
|
461
|
+
raise ValueError(f"Operands do not produce a canonical {kind.value} directive.")
|
|
462
|
+
|
|
463
|
+
|
|
387
464
|
def decompose_directive(text: str) -> CanonicalDirective | InvalidDirectiveSyntax | None:
|
|
388
465
|
"""Parse one canonical directive into its semantic kind and operands.
|
|
389
466
|
|
|
@@ -405,19 +482,14 @@ def decompose_directive(text: str) -> CanonicalDirective | InvalidDirectiveSynta
|
|
|
405
482
|
normalized = _normalized_for_matching(trimmed_text)
|
|
406
483
|
|
|
407
484
|
if normalized == _CLEAR_PREMISE_TEXT:
|
|
408
|
-
return CanonicalDirective(
|
|
409
|
-
text=text, kind=DirectiveKind.CLEAR_PREMISE, operands=MappingProxyType({})
|
|
410
|
-
)
|
|
485
|
+
return CanonicalDirective(kind=DirectiveKind.CLEAR_PREMISE, operands=MappingProxyType({}))
|
|
411
486
|
if normalized == _RESET_POLICIES_TEXT:
|
|
412
487
|
return CanonicalDirective(
|
|
413
|
-
text=text,
|
|
414
488
|
kind=DirectiveKind.RESET_POLICIES,
|
|
415
489
|
operands=MappingProxyType({}),
|
|
416
490
|
)
|
|
417
491
|
if normalized == _CLEAR_STATE_TEXT:
|
|
418
|
-
return CanonicalDirective(
|
|
419
|
-
text=text, kind=DirectiveKind.CLEAR_STATE, operands=MappingProxyType({})
|
|
420
|
-
)
|
|
492
|
+
return CanonicalDirective(kind=DirectiveKind.CLEAR_STATE, operands=MappingProxyType({}))
|
|
421
493
|
|
|
422
494
|
if normalized == "set premise":
|
|
423
495
|
return _invalid_directive_syntax(
|
|
@@ -440,7 +512,6 @@ def decompose_directive(text: str) -> CanonicalDirective | InvalidDirectiveSynta
|
|
|
440
512
|
directive_kind=DirectiveKind.SET_PREMISE,
|
|
441
513
|
)
|
|
442
514
|
return CanonicalDirective(
|
|
443
|
-
text=text,
|
|
444
515
|
kind=DirectiveKind.SET_PREMISE,
|
|
445
516
|
operands=MappingProxyType({"value": value}),
|
|
446
517
|
)
|
|
@@ -468,7 +539,6 @@ def decompose_directive(text: str) -> CanonicalDirective | InvalidDirectiveSynta
|
|
|
468
539
|
missing_operand="value",
|
|
469
540
|
)
|
|
470
541
|
return CanonicalDirective(
|
|
471
|
-
text=text,
|
|
472
542
|
kind=DirectiveKind.CHANGE_PREMISE,
|
|
473
543
|
operands=MappingProxyType({"value": value}),
|
|
474
544
|
)
|
|
@@ -518,7 +588,6 @@ def decompose_directive(text: str) -> CanonicalDirective | InvalidDirectiveSynta
|
|
|
518
588
|
directive_kind=DirectiveKind.USE_ITEM,
|
|
519
589
|
)
|
|
520
590
|
return CanonicalDirective(
|
|
521
|
-
text=text,
|
|
522
591
|
kind=DirectiveKind.USE_ITEM,
|
|
523
592
|
operands=MappingProxyType({"item": item}),
|
|
524
593
|
)
|
|
@@ -546,7 +615,6 @@ def decompose_directive(text: str) -> CanonicalDirective | InvalidDirectiveSynta
|
|
|
546
615
|
missing_operand="item",
|
|
547
616
|
)
|
|
548
617
|
return CanonicalDirective(
|
|
549
|
-
text=text,
|
|
550
618
|
kind=DirectiveKind.PROHIBIT_ITEM,
|
|
551
619
|
operands=MappingProxyType({"item": item}),
|
|
552
620
|
)
|
|
@@ -574,7 +642,6 @@ def decompose_directive(text: str) -> CanonicalDirective | InvalidDirectiveSynta
|
|
|
574
642
|
missing_operand="item",
|
|
575
643
|
)
|
|
576
644
|
return CanonicalDirective(
|
|
577
|
-
text=text,
|
|
578
645
|
kind=DirectiveKind.REMOVE_POLICY,
|
|
579
646
|
operands=MappingProxyType({"item": item}),
|
|
580
647
|
)
|
|
@@ -588,40 +655,23 @@ def get_directive_metadata() -> tuple[DirectiveMetadata, ...]:
|
|
|
588
655
|
return _PUBLIC_DIRECTIVE_METADATA
|
|
589
656
|
|
|
590
657
|
|
|
591
|
-
def
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
raise ValueError(f"Unsupported directive kind: {kind!r}") from exc
|
|
658
|
+
def _serialize_canonical_directive(
|
|
659
|
+
kind: DirectiveKind | str, operands: MappingProxyType[str, str]
|
|
660
|
+
) -> str:
|
|
661
|
+
"""Serialize a validated semantic directive without reparsing it."""
|
|
662
|
+
normalized_kind = _normalize_directive_kind(kind)
|
|
663
|
+
spec = _DIRECTIVE_SPECS[normalized_kind]
|
|
598
664
|
|
|
599
|
-
|
|
600
|
-
actual_names = set(operands)
|
|
601
|
-
unexpected_names = actual_names - expected_names
|
|
602
|
-
missing_names = expected_names - actual_names
|
|
603
|
-
if missing_names:
|
|
604
|
-
missing = ", ".join(sorted(missing_names))
|
|
605
|
-
raise ValueError(f"Missing required operands for {normalized_kind.value}: {missing}")
|
|
606
|
-
if unexpected_names:
|
|
607
|
-
unexpected = ", ".join(sorted(unexpected_names))
|
|
608
|
-
raise ValueError(f"Unexpected operands for {normalized_kind.value}: {unexpected}")
|
|
665
|
+
return spec.renderer(operands)
|
|
609
666
|
|
|
610
|
-
normalized_operands: dict[str, str] = {}
|
|
611
|
-
for name in spec.operand_names:
|
|
612
|
-
raw_value = operands[name]
|
|
613
|
-
if not isinstance(raw_value, str):
|
|
614
|
-
raise ValueError(f"Operand {name!r} for {normalized_kind.value} must be a string.")
|
|
615
|
-
if raw_value.strip() == "":
|
|
616
|
-
raise ValueError(f"Operand {name!r} for {normalized_kind.value} cannot be empty.")
|
|
617
|
-
normalized_operands[name] = raw_value
|
|
618
667
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
668
|
+
def _render_directive(kind: DirectiveKind | str, /, **operands: str) -> str:
|
|
669
|
+
"""Produce canonical directive text from a semantic kind and operands."""
|
|
670
|
+
normalized_kind = _normalize_directive_kind(kind)
|
|
671
|
+
return CanonicalDirective(
|
|
672
|
+
kind=normalized_kind,
|
|
673
|
+
operands=MappingProxyType(dict(operands)),
|
|
674
|
+
).text
|
|
625
675
|
|
|
626
676
|
|
|
627
677
|
__all__ = [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/01_llm_contradiction_error.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/02_llm_constraint_guardrail.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/03_llm_premise_guardrail.py
RENAMED
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/05_llm_prompt_drift_vs_state.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/06_llm_context_compaction.py
RENAMED
|
File without changes
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/demos/07_llm_prompt_vs_state.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/01_persistent_guardrails.py
RENAMED
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/03_ambiguity_with_error.py
RENAMED
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/examples/05_llm_integration_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_compiler-0.9.0.dev10 → context_compiler-0.9.0.dev12}/src/context_compiler/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|