All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.1.0] - 2026-08-04
Added
- Initial extraction from Aletheia: term representation, clause database, SLD-resolution engine (with a genuine clause-scoped cut), and the arithmetic/exceptions/lists/io builtin predicates, split out into their own package so they're usable without Aletheia's reader/syntax layer.
- A dynamic database:
assert/1,asserta/1,assertz/1,retract/1,retractall/1. Effects persist across separateEpisteme.query/2calls against the sameDatabase.t(), not just within one. - Solution aggregation:
findall/3,forall/2. between/3inEpisteme.Builtins.Arithmetic.- Pluggable clause storage via the new
Episteme.Database.Backendbehaviour:Episteme.Database.Backends.Ets(the default, in-memory, indexed by{name, arity}) andEpisteme.Database.Backends.Dets(the same shape, persisted to disk).Database.new/1takes a:backendoption;Database.close/1andDatabase.sync/1are new. - TUTORIAL.md (a from-scratch walkthrough), REFERENCE.md (every control construct, comparison, type check, arithmetic feature, and exception/database/list/I/O predicate, in full detail with a verified example each), EXAMPLES.md (complete, verified-runnable programs), CHEATSHEET.md (a one-page predicate/function reference), and CONTRIBUTING.md.
Changed
- Breaking: control constructs and comparison operators are now
spelled as plain English words instead of ISO Prolog's punctuation
operators, since Episteme has no reader of its own for that
punctuation to be conventional syntax against:
,→and/2,;→or/2,!→cut,\+→not/1(Cond -> Then ; Else)→if_then_else/3(new, explicit 3-arity goal, replacing the old ";wrapping a->" nested-pattern special case);(Cond -> Then)→if_then/2=→unify/2,\=→not_unify/2,==→equal/2,\==→not_equal/2=:=→numeric_equal/2,=\=→numeric_not_equal/2,=<→less_or_equal/2,>=→greater_or_equal/2+,-,*,/,<,>are unchanged — ordinary math notation, not Prolog-specific punctuation, and arithmetic expression functors (+,-,*,/,//,mod,rem,**,^,abs,sign,min,max,sqrt) are entirely unaffected, since those were never goals to begin with. No aliases: the old symbol-based names are gone, not deprecated.
- Breaking:
Database.t()now wraps a mutable backend resource (an ETS table by default) rather than being a plain immutable struct —assert/retractneed a database whose mutations are visible to every holder of the same value.add_clause/2,add_fact/2,clauses_for/3,defined?/3,indicator/1, andconsult_forms/2keep their existing signatures and behavior;Database.new/0still works exactly as before (now sugar forDatabase.new([])). - The
precommitalias now runssobelow --skip, honoring the# sobelow_skip [...]justification comment onEpisteme.query_once/2(a documented low-confidence false positive) instead of only silencing it by happenstance ofsobelow's own default exit code. ichor_runtimerequirement bumped from~> 0.1.0to~> 0.2, resolving to0.2.0. That release's breaking change (raw_capturesbecoming an ordered list of pairs instead of a map) is confined toIchor.Actions/Ichor.Capture/the grammar runtime, none of which Episteme touches — it only usesIchor.Backtrack.*andIchor.Toolkit.TermWalk, both unaffected, so this is a no-op for Episteme's own behavior.
Fixed
Episteme.query/2(andquery_once/2/query_lazy/2) no longer crashes with a rawFunctionClauseErrorwhen a partial list (one ending in a variable or other non-list, non-[]tail, e.g.[1 | X]) appears anywhere in the top-level goal — the internal walk that finds which variables to report in the answer usedEnum.reduce/3, which only works on proper (nil-terminated) lists.length/2with an unbound list and a bound count built one fresh variable and duplicated the same one that many times instead of generating independent fresh variables, so e.g.and(length(L, 3), unify(L, [a, b, c]))wrongly failed (the shared variable couldn't unify with two different values at once).- The package
descriptioninmix.exsexceeded Hex's 300-character limit, somix hex.build(and thusmix hex.publish, and the CI job that runshex.buildto verify the package assembles) failed outright. Trimmed to fit while keeping the same content.