Full-Text Search (BM25)

fuzzy does pairwise string similarity and skill_library does alphabetical substring matching, but neither ranks a corpus of documents by relevance — a rare distinctive term and a ubiquitous one weigh the same. This adds an inverted-index search that ranks documents with Okapi BM25 (or TF-IDF), so flows and agents can search logs, scraped records, or knowledge snippets without a database.

Pure standard library (math + collections + re); deterministic; imports no PySide6.

Headless API

from je_auto_control import SearchIndex, search_documents

corpus = {
    "d1": "the quick brown fox jumps over the lazy dog",
    "d2": "a quick brown dog runs fast",
    "d3": "the database stores quick query results",
}

index = SearchIndex.build(corpus)          # or SearchIndex(); index.add(id, text)
for hit in index.search("quick dog", top_k=5):
    print(hit.doc_id, hit.score)

# one-shot convenience
hits = search_documents(corpus, "database", mode="bm25")

SearchIndex.add / remove keep the index up to date incrementally; build indexes a {doc_id: text} map (or (id, text) pairs). search returns ranked SearchHit(doc_id, score) results — by default BM25 (k1=1.5, b=0.75), or mode="tfidf". The scoring is the standard Okapi formula with IDF = ln(1 + (N df + 0.5) / (df + 0.5)), so a rare term out-ranks a common one, term-frequency saturates (k1), and long documents are normalized down (b). A stop_words set can be supplied to drop noise terms. Results are deterministic (ties broken by doc_id).

Executor command

AC_search_documents takes docs (a {doc_id: text} map or JSON string), a query, and optional top_k / mode; it returns {hits: [{doc_id, score}]}. The same operation is exposed as the MCP tool ac_search_documents and as a Script Builder command under Data.