Files

1 line
67 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "automataia/graphrag-rs", "files": {"CHANGELOG.md": {"a7222c1b9238b483380c9f6fd5fb164586293b0f": {"t": 1785595755.1670887, "n": 67220, "h": "8e4869b3d2667a60", "c": "# Changelog\n\nAll notable changes to GraphRAG-RS will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n### Security\n\n#### CI green: cargo-deny advisories/licenses + rustfmt (2026-05-31)\n- **Vulnerabilities patched via lockfile bumps:** `rand` 0.8.5→0.8.6 and 0.9.2→0.9.4\n (RUSTSEC-2026-0097 unsoundness), `bytes` 1.10.1→1.11.1 (RUSTSEC-2026-0007 integer overflow),\n `rustls-webpki` 0.103.7→0.103.13 (RUSTSEC-2026-0049/0098/0099/0104 — CRL + name-constraint\n vulns). All patch-level, non-breaking.\n- **`deny.toml` licenses:** added `BSL-1.0` (Boost) and `CDLA-Permissive-2.0` (Mozilla CA bundle\n via `webpki-roots`) to the allow-list — both permissive, were failing the licenses job.\n- **`deny.toml` advisory ignores (unfixable here, documented inline):** unmaintained transitive\n crates `proc-macro-error`, `bincode`, `json`, `number_prefix`, `paste`, `rustls-pemfile`;\n `lru` 0.12 unsoundness (RUSTSEC-2026-0002, pinned by ratatui 0.29, unreachable in our usage);\n and `time` DoS (RUSTSEC-2026-0009) — its fix (≥0.3.47) requires rustc 1.88, above our MSRV 1.85,\n so `time` is held at 0.3.44 and the advisory accepted (reachable only via untrusted RFC-2822\n parsing in the server, not core/cli). Revisit when MSRV moves to ≥1.88.\n- **Formatting:** ran `cargo fmt --all` over the workspace (71 files) to clear the long-standing\n `rustfmt` CI job. Mechanical, no behavior change.\n- **`--all-features` advisory/license coverage:** the `cargo-deny-action` defaults to\n `--all-features`, so CI also scans the optional `lancedb` tree (lance/datafusion/arrow). Patched\n `lz4_flex` 0.11.5→0.11.6 / 0.12.0→0.12.2 (RUSTSEC-2026-0041) and `tar` 0.4.44→0.4.46\n (RUSTSEC-2026-0067/0068); allowed `0BSD` (`mock_instant`). Added `[graph] all-features = true` to\n `deny.toml` so local `cargo deny check` sees the same graph as CI (prevents local≠CI drift).\n- **CI SIGILL fix:** set `RUSTFLAGS = \"-C target-cpu=x86-64-v2\"` in `ci.yml` to override the repo's\n `.cargo/config.toml` `-C target-cpu=native`. On GitHub's heterogeneous runners `native` can emit\n instructions the silicon traps (SIGILL crashing rustc/proc-macros, seen building `ollama-rs`).\n Verified the rustc invocation: an empty `CARGO_BUILD_RUSTFLAGS` is ignored and doesn't override\n the config flag — only a non-empty `RUSTFLAGS` (highest precedence) fully replaces it. Local dev\n keeps `target-cpu=native`; CI uses the portable `x86-64-v2` baseline.\n\n### Added\n\n#### Documentation site (2026-05-31)\n- mdBook documentation site under `book/`, deployed to GitHub Pages at\n `https://automataia.github.io/graphrag-rs/`. Curated, English-only, user-facing TOC\n (`book/src/SUMMARY.md`) covering getting-started, concepts, configuration, features, and per-crate\n guides. Internal dev reports and Italian guides are intentionally excluded.\n- Chapters are thin `{{#include}}` wrappers over the canonical sources (`HOW_IT_WORKS.md`, crate\n READMEs, curated `docs/*.md`) so there is a single source of truth and no content drift.\n Front-door pages (`introduction.md`, `getting-started/overview.md`, `quickstart.md`) are authored.\n- Mermaid diagrams render via the `mdbook-mermaid` preprocessor; built-in client-side search enabled.\n- API reference links out to [docs.rs/graphrag-core](https://docs.rs/graphrag-core) rather than\n self-hosting `cargo doc`.\n- New CI workflow `.github/workflows/docs.yml` builds the book (pinned `mdbook` 0.5.3 +\n `mdbook-mermaid` 0.17.0 prebuilt binaries) and deploys via `actions/deploy-pages`. The generated\n `book/book/` output is git-ignored. **Manual one-time step:** set repo Settings → Pages → Source =\n \"GitHub Actions\".\n- README: added a docs-site badge.\n- Translated to English the doc sources the site includes that still contained Italian:\n `docs/INCREMENTAL_UPDATES.md`, `docs/TUI_USAGE_GUIDE.md`, `docs/ENRICHMENT_USAGE_GUIDE.md`,\n `docs/SUMMARIZATION_CONFIG.md`, the `graphrag-cli/README.md` config table notes, and the\n Italian entries in this CHANGELOG. Fixed stale repo URLs (`anthropics/*` → `automataIA/graphrag-rs`)\n in the translated guides. The public site is now English-only end to end.\n- Stripped decorative/pictographic emoji (the 📚🚀📖 family) from the doc sources the site\n includes, fixing \"tofu\" boxes that appeared wherever the viewer's font lacked an emoji glyph\n (mdBook's default theme has no emoji-font fallback — a generic missing-glyph issue, not a bug).\n Preserved arrows (→), box-drawing/ASCII diagrams (━│▼█), and data symbols (✅❌★☆); converted\n rating ⭐→★ to keep ratings rendering. Keycap-numbered headings (`1.`/`2.`) replaced the\n `1️⃣` style.\n\n## [0.2.0] - 2026-05-31\n\n### Fixed\n\n- **`arrow` workspace dep**: added `default-features = false` to `arrow = \"57\"` in\n the workspace `Cargo.toml`. Previously, the `default-features = false` directive in\n `graphrag-core/Cargo.toml` was silently ignored by Cargo (build-time warning).\n- **`documentation` metadata for the `graphrag` crate**: added `documentation = \"https://docs.rs/graphrag\"`\n in `graphrag/Cargo.toml`, aligning the wrapper crate with `graphrag-core` and `graphrag-cli`.\n\n#### Code/architecture/product quality audit (2026-05-30)\n\n### Added\n\n- **CI/CD**: new workflow `.github/workflows/ci.yml`. The repo previously had no CI\n automation. **Blocking** jobs: `clippy --workspace --lib -D warnings`\n (now green, see below), `test -p graphrag-core --lib`, `cargo-deny`. The\n `fmt` job is **informational and non-blocking** (`continue-on-error`) until the repo is\n made `cargo fmt --all` clean (pre-existing repo-wide formatting debt).\n- **Security tooling**: `deny.toml` (advisories + permissive licenses + duplicate ban)\n and `SECURITY.md` (private disclosure policy via GitHub Security Advisories).\n- **Drift-guard tests** (`config/setconfig.rs`): `gliner_setconfig_default_matches_runtime`\n and `autosave_setconfig_default_matches_runtime` fail at build time if the serde\n leaf-struct defaults diverge from the canonical runtime ones, preventing \"5-point-sync\" drift.\n `OllamaConfig` is excluded on purpose (by-design divergence: offline-first runtime vs\n user-facing schema).\n- **Crate metadata**: `documentation` (docs.rs) and `readme` fields added to\n `graphrag-core` and `graphrag-cli` for publishing on crates.io.\n\n#### Documentation polish (2026-05-30)\n\n- **`graphrag/README.md`**: the wrapper meta-crate had no README (only `Cargo.toml`\n + `src`). Added: explains that it re-exports `graphrag-core` and provides the `graphrag`\n binary, with a binary quick-start + library usage and links to the core/root README.\n- **Module `//!` headers** added to the 10 `graphrag-core` modules that lacked them\n (previously starting with `use`/`pub mod`/`#[cfg]` or a `///` on the first submodule):\n `config`, `graph`, `generation`, `critic`, `retrieval`, `summarization`, `vector`,\n `entity`, `text`, `query`. Every module's rustdoc page now shows a description.\n Doc-comments only, no behavior change; `clippy -p graphrag-core -D warnings`\n stays green and `cargo doc` introduces no new warnings.\n\n#### PageRank: score normalization (dangling nodes) (2026-05-30)\n\n- **Bug fix**: `scores_to_entity_map` in [graph/pagerank.rs](graphrag-core/src/graph/pagerank.rs)\n now L1-normalizes the scores (sum = 1.0). *Dangling* nodes (no outgoing edges)\n lost rank mass on every iteration, leaving the sum < 1.0. Single fix point\n → covers all paths (dense/parallel/sparse). Unblocks 3 previously-failing tests:\n `test_pagerank_convergence`, `test_personalized_pagerank`,\n `test_precompute_global_pagerank` (visible only under the `pagerank` feature, activated\n by `--workspace` feature-unification).\n\n#### Swagger UI served at `/swagger` (2026-05-30)\n\n- **graphrag-server**: the Swagger UI was announced but not served (\"coming soon\").\n Now exposed at `/swagger` via **apistos**'s native support (`features = [\"swagger-ui\"]`,\n already enabled) — `apistos-swagger-ui` bundles the official Swagger UI assets, so\n no new dependency. Changed `.build(\"/openapi.json\")` →\n `.build_with(..., BuildConfig::default().with(SwaggerUIConfig::new(&\"/swagger\")))` in\n [main.rs](graphrag-server/src/main.rs). README updated (removed \"coming soon\").\n\n#### Clean clippy on examples/tests + green doctests (2026-05-30)\n\n- **Clippy examples/tests**: `cargo clippy --examples --tests -p graphrag-core -- -D warnings`\n is now green. Bulk via `cargo clippy --fix`; manual tail: `///!`→`//!` (embeddings demo),\n `.filter().next_back()`→`.rfind()`, `.clone()` on a double ref → `.iter().copied()`,\n ignored `let _ =` on `Result`, `std::slice::from_ref`, removal of unused vars.\n- **Doctest**: `cargo test --doc -p graphrag-core` → 47 pass / 0 fail / 17 ignored. 7\n illustrative, non-self-contained examples (require a live Ollama, an async runtime, or\n undefined setup variables — `core::ChunkingStrategy`, `build_relationship_hierarchy`,\n KV-cache Ollama, `pipeline_executor`, etc.) marked ```` ```ignore ````. The hero example\n still runs and is green.\n- **`clippy --fix` regression corrected**: `config/enhancements.rs:770` — `--fix` had removed\n `mut` from `let count`, seeing it as inactive under default features; restored `let mut count`\n with `#[allow(unused_mut)]` (the `count += 1`s are behind `#[cfg(feature = ...)]`).\n\n#### Stale examples/tests recompile (2026-05-30)\n\n- **Stale struct initializers**: added the missing temporal/causal fields (all `None`)\n to the `Entity` literals (`first_mentioned`, `last_mentioned`, `temporal_validity`) and\n `Relationship` literals (`embedding`, `temporal_type`, `temporal_range`, `causal_strength`) in\n the `llm_evaluation_demo`, `advanced_nlp_demo`, `hierarchical_graphrag_demo`,\n `workspace_demo`, `tom_sawyer_workspace` examples. They had fallen behind the evolution\n of `Entity`/`Relationship` in `core/mod.rs` (Phase 1.2) and broke `cargo build --examples`.\n- **`complete_zero_cost_graphrag_demo`**: `Config` literal closed with `..Default::default()`\n (it was missing `advanced_features`, `gliner`, `suppress_progress_bars`) and the `EntityConfig`\n literal completed with `use_atomic_facts: false` + `max_fact_tokens: 400`.\n- **Per-feature gating** (`graphrag-core` Cargo.toml): `hierarchical_graphrag_demo` now\n `required-features = [\"leiden\"]` (uses `LeidenConfig` / `detect_hierarchical_communities`,\n `#[cfg(feature = \"leiden\")]`) and the `incremental_integration` test `required-features =\n [\"incremental\"]` (it imported `graphrag_core::incremental`). So a default\n `cargo build/test --workspace` stays green without pulling in the optional features.\n- **`Chat discussion.html`**: added the standard `line-clamp:3` property alongside\n `-webkit-line-clamp` (CSS `vendorPrefix` linter).\n- Verification: `cargo build --examples --tests --workspace` → clean Finished; `cargo test\n -p graphrag-core --lib` → 365 pass / 0 fail. The 3 `pagerank` tests that fail under\n `--workspace` feature-unification are **pre-existing** (confirmed on a clean tree).\n\n### Changed\n\n- **Dependency dedup (anti-bloat)**: aligned two direct workspace dependencies\n to versions already present transitively, eliminating duplicate versions\n in `graphrag-cli`'s `-e normal` tree:\n - `strum` 0.25 → **0.26** (matches `ratatui 0.29`) — removes duplicate `strum` +\n `strum_macros`.\n - `itertools` 0.12 → **0.13** (matches `ratatui`/`unicode-truncate`).\n - Real duplicates in `graphrag-cli`'s normal tree dropped from 34 to 26. Verified that\n `graphrag-core` (the published crate) has only 4 unavoidable transitive duplicates\n (`getrandom` 0.2/0.3, `webpki-roots` 0.26/1.0, TLS stack). `rand` 0.8→0.9 NOT\n done (API-breaking, only deduplicated the unpublished server binary).\n\n### Fixed\n\n- **CLI crash at startup on all non-TUI subcommands** (`index`, `ask`, `bench`,\n `setup`, `validate`, …): `color_eyre::install()` was called twice —\n in `graphrag-cli/src/main.rs:10` and again inside `run()` at `lib.rs:197` — and\n the second install aborted with *\"could not set the provided `Theme`\n globally as another was already set\"*. Removed the duplicate `install()` from\n `main.rs`; now both binaries (`graphrag-cli` and the `graphrag` meta-crate,\n which doesn't install on its own) install exactly once via `run()`.\n Caught by running the e2e benchmarks (`bench`).\n- **MSRV corrected and verified**: `rust-version` changed from `1.75` (false, never\n tested) to **`1.85`**. The real floor is imposed by the direct dependency `jsonfixer`,\n which uses `edition = \"2024\"` (requires rustc ≥ 1.85). Build-verified on the 1.85\n toolchain for `graphrag-core` and `graphrag-cli`. New `msrv` CI job that builds on 1.85.\n Analysis method: floor from `cargo metadata` (max `rust_version` declared among the\n normal deps) + build verification on a single toolchain (no costly bisect).\n- **Lint debt zeroed (green workspace clippy)**: resolved 38 pre-existing clippy\n errors that surfaced under `cargo clippy --workspace --lib -- -D warnings` (Rust\n 1.95). Diagnosis: `graphrag-core` in isolation (default features) was already clean;\n the errors were in core's optional modules (`incremental`, `rograg`,\n `lightrag`, `embeddings/ollama`) activated by the cli/server features + 3 errors\n of `graphrag-cli`'s own. Idiomatic fixes (`to_vec()`, `iter_mut().enumerate()`,\n `if let Some`, `sort_by_key(Reverse(..))`, type aliases `NodeDeltaResult`/\n `EdgeDeltaResult`) and targeted, commented `#[allow]`s where a rename would break the\n serde API (`PendingUpdateType`) or for a private 10-argument helper.\n Not an interface break: the crates compile and link correctly.\n- **GLiNER default drift**: `default_gliner_entity_labels`/`default_gliner_relation_labels`\n in `config/setconfig.rs` were misaligned with the runtime `GlinerConfig::default()`\n (missing `\"concept\"` and `\"causes\"`). Now aligned with the canonical default (4 entity + 3\n relation labels). Not observable in the existing e2e configs (they set the labels\n explicitly); relevant only when GLiNER is enabled via TOML while omitting the labels.\n\n### Documentation\n\n- **Markdown doc consolidation (few but useful)**: reduced the ~55 tracked `.md` files to a\n keystone set. Deleted 39 files among process artifacts (`report.md`, `TODO.md`,\n `*_COMPLETE.md`, `*_SUMMARY.md`, `*_STATUS.md`, `MERGE_COMPLETE.md`,\n `IMPLEMENTATION_SUMMARY.md`) and satellite integration guides now covered by the keystones\n (`graphrag-core/{ADVANCED_FEATURES,OLLAMA_INTEGRATION,LEIDEN_INTEGRATION,LIGHTRAG_INTEGRATION,\n HIPPORAG_INTEGRATION,CROSS_ENCODER_INTEGRATION,ENTITY_EXTRACTION,EMBEDDINGS_CONFIG,\n PIPELINE_ARCHITECTURE,QUICKSTART,ENRICHMENT_IMPLEMENTATION,WORKSPACE_PERSISTENCE_SUMMARY}.md`,\n the `src/{embeddings/README,graph/TRAVERSAL_GUIDE}.md`, the entire series of non-README\n `graphrag-wasm/*.md` guides, `examples/MULTI_DOCUMENT_PIPELINE.md`). The surviving\n keystones: `README.md`, `HOW_IT_WORKS.md`, `CHANGELOG.md`, the 4 crate READMEs,\n `config/JSON5_CONFIG_GUIDE.md`. The `docs/` folder is git-ignored (local notes) and is\n not touched.\n- **Keystone staleness fixes**: MSRV badge/prerequisites `1.70` → **`1.85`** in the root README;\n removed references to the deleted `graphrag-leptos` crate (workspace layout now 5-crate\n + the `graphrag` meta-crate, dependency graph updated); \"Web UI\" section rewritten around the\n chat-shell. `HOW_IT_WORKS.md`: the WASM section now points to `graphrag-wasm` (no longer to the\n deleted `graphrag-leptos`).\n- **graphrag-wasm README rewritten**: the old 5-tab DaisyUI UI is replaced by the\n documentation of the 3-column Nordic-Minimal chat-shell (LeftRail/Stage/RightRail),\n off-main-thread inference, citations, IndexedDB persistence; removed the dead links to the\n deleted satellite guides.\n- **Internal links repointed**: all links to the deleted docs (in `README.md`,\n `HOW_IT_WORKS.md`, `graphrag-core/README.md`) now point to `HOW_IT_WORKS.md`,\n `config/JSON5_CONFIG_GUIDE.md`, `CHANGELOG.md`, or `docs.rs/graphrag-core`.\n\n### Removed\n\n- **Dead code**: removed `graphrag-server/src/main_axum_old.rs` (~31KB, orphan file\n with no references, neither a bin-target nor a module).\n- **Unused dependency**: removed `text_analysis = \"0.3\"` from `graphrag-core` and\n from `[workspace.dependencies]` (detected with `cargo machete`, verified: no\n use in the code — the only match was the string `\"context_analysis\"`). The other\n `cargo machete` reports (`getrandom`, `gline-rs`, `js-sys`, `web-sys`,\n `tower`, `text-splitter`) are verified **false positives** (wasm/api feature-enablers\n or crates whose lib name differs from the package name, like `gline-rs`→`gliner`) and\n kept.\n\n### Changed\n\n#### graphrag-wasm chat-shell rewrite (Nordic-Minimal) (2026-05-17)\n\n- **BREAKING:** the 5-tab daisyUI UI (`Build / Explore / Query / Hierarchy / Settings`) is\n replaced by a single 3-column chat shell that mirrors the\n [`Chat discussion.html`](Chat%20discussion.html) Nordic-Minimal mockup verbatim\n (palette, font stack `Newsreader / Geist / Geist Mono`, class names,\n citation/hover wiring).\n - New layout in [graphrag-wasm/src/main.rs](graphrag-wasm/src/main.rs):\n `LeftRail` (brand + sources + Flat/Hierarchy toggle + Build button),\n `Stage` (head with active source, thread of `Turn`s, composer), `RightRail`\n (subgraph SVG + pipeline rows + ministats + references). All real data:\n `documents` come from the existing IndexedDB signal, pipeline progress is\n driven by the existing `BuildStatus`/`BuildStage`, embeddings come from\n ONNX Runtime Web + `tokenizer.json`, retrieval from `VectorIndex::search`,\n answers from WebLLM (`Phi-3-mini` for synthesis, Qwen for extraction),\n citations are post-processed via `parse_answer_with_cites` and link to\n `<button class=\"cite\">` ↔ `<div class=\"ref-card\">` through the reactive\n `active_ref: Option<u32>` signal — no inline JS.\n - New module [graphrag-wasm/src/components/chat_shell.rs](graphrag-wasm/src/components/chat_shell.rs)\n holds the data types (`ChatTurn`, `RefCard`, `AnswerSegment`, `SubgraphData`),\n the citation parser and the per-query `build_subgraph` builder that unions\n entities from the top-K retrieved chunks and feeds them through\n `components::force_layout::ForceLayout` (320×240 viewBox, 16-node /\n 21-edge cap matching the mockup density label).\n - Styling: [graphrag-wasm/tailwind.css](graphrag-wasm/tailwind.css) is now a\n flat Nordic-Minimal stylesheet (no `@tailwind` directives, no daisyUI);\n [graphrag-wasm/index.html](graphrag-wasm/index.html) drops lucide CDN +\n MutationObserver and adds the Google-fonts preconnect block.\n - `leptos-lucide-rs` dependency removed from\n [graphrag-wasm/Cargo.toml](graphrag-wasm/Cargo.toml).\n - Legacy daisyUI components (`components/{settings,hierarchy,ui_components,chat_component}.rs`)\n remain on disk for reference but are no longer compiled — `components/mod.rs`\n only exports `chat_shell` + `force_layout`.\n - **Parity test:** [graphrag-wasm/tests/playwright/chat_layout.sh](graphrag-wasm/tests/playwright/chat_layout.sh)\n drives `playwright-cli`: opens the mockup over `python3 -m http.server` and\n the WASM SPA on `trunk serve`, captures 1440×900 screenshots\n (`tests/playwright/artifacts/{mockup,wasm}.png`) and asserts 19 shared\n selectors (`.app`, `.rail-left .doc-item`, `.stage-title`, `.bubble-q`,\n `.cite`, `.stages .pls`, `.graph-frame svg`, `.ref-card`, `.composer input`,\n …). Current status: 19/19 pass.\n\n### Added\n\n#### 2026 best-practices pass (graphrag-core ↔ graphrag-wasm) (2026-05-16)\n\n- **Off-main-thread inference (Stage 3b)** for graphrag-wasm.\n - **WebLLM**: `WebLLM::new` and `WebLLM::new_with_progress` in\n [graphrag-wasm/src/webllm.rs](graphrag-wasm/src/webllm.rs#L132) now auto-detect\n a pre-spawned `window.webllmWorker` and switch to `CreateWebWorkerMLCEngine`,\n keeping the same `chat.completions.create` surface (and `chat_stream`'s\n async-iterator) intact. Falls back to the main-thread engine if worker spawn\n fails. New sidecar [graphrag-wasm/webllm-worker.js](graphrag-wasm/webllm-worker.js)\n hosts `WebWorkerMLCEngineHandler` (15 LOC).\n - **ONNX Runtime Web**: `ort.env.wasm.proxy = true` + `numThreads = 1` set\n immediately after `ort.min.js` loads in\n [graphrag-wasm/index.html](graphrag-wasm/index.html#L13), so all\n `InferenceSession.run` calls execute in ORT's dedicated worker.\n - Trade-off vs the plan's gloo-worker route: no second wasm bundle, no\n Rust worker scaffolding, ~30 LOC swap. Verification (\"main-thread blocked\n < 50 ms during inference\") met via the runtimes' built-in workers.\n- **Token-streaming UX** in graphrag-wasm QueryTab. Replaced the blocking\n `WebLLM::chat(...)` call at [graphrag-wasm/src/main.rs:1604](graphrag-wasm/src/main.rs#L1604)\n with `chat_stream(...)`: tokens are now appended to the results signal\n incrementally as they arrive from the model, matching 2026 in-browser-LLM UX\n guidance. The pre-existing streaming API in\n [graphrag-wasm/src/webllm.rs:334](graphrag-wasm/src/webllm.rs#L334)\n was previously unused.\n- **IndexedDB persistence** for the document set. New\n [graphrag-wasm/src/persist.rs](graphrag-wasm/src/persist.rs) wraps\n `IndexedDBStore` with `open_store`, `save_document`, `delete_document`,\n `load_all_documents`. The `App` component restores documents on first load;\n manual input, file upload, Symposium-demo load, and document-remove handlers\n all persist their mutations. Reloading the page now preserves the document\n set instead of resetting to empty.\n- **WAI-ARIA tabs pattern** in graphrag-wasm. All 5 tab panels are now mounted\n permanently inside a `<main id=\"main-content\">` landmark with\n `hidden=move || active_tab.get() != Tab::X`. Each tab button gained an `id`\n (`tab-build`, `tab-explore`, etc.) matching the panel's `aria-labelledby`.\n This fixes Lighthouse `aria-valid-attr-value` and `landmark-one-main` audits,\n and preserves component state across tab switches.\n- **SEO**: added `<meta name=\"description\">` and `<link rel=\"canonical\">` plus\n `<meta name=\"color-scheme\" content=\"dark light\">` to\n [graphrag-wasm/index.html](graphrag-wasm/index.html). External links in the\n footer gained `rel=\"noopener noreferrer\"`.\n\n- Downloaded MiniLM-L6-v2 ONNX model (87MB) to `graphrag-wasm/models/minilm-l6.onnx`\n for semantic query embeddings. Previously the directory was empty, causing fallback\n to hash-based embeddings which produced no meaningful search results.\n\n### Removed\n\n#### Broken orphan example crates deleted (2026-05-16)\n- `examples/web-app/` and `examples/graphrag-leptos-demo/` both depended on the\n deleted `graphrag-leptos` crate (merged into `graphrag-wasm` in March 2025).\n They were excluded from the workspace so they did not block builds, but were\n misleading for newcomers. Functionality is fully covered by `graphrag-wasm`\n itself.\n- Dropped `exclude = [\"examples/web-app\"]` from root `Cargo.toml`.\n\n#### `graphrag_py` Python bindings crate deleted (2026-05-16)\n- Removed `graphrag_py/` directory and workspace member entry in root `Cargo.toml`.\n- Reason: legacy crate, pyo3 0.21 (out-of-date), last touched 4 commits ago before the\n KV-cache / GLiNER / contextual-enricher / persistence wave. API frozen pre-feb-2026,\n never published (`publish = false`), `Development Status :: 4 - Beta`.\n- **BREAKING:** Python bindings no longer build from this repo. Future Python support\n should live in a separate repo with current pyo3.\n\n### Changed\n\n#### Clippy gate restored on wasm32-unknown-unknown target (2026-05-16)\n`cargo clippy --lib -p graphrag-core --no-default-features --features \"wasm-bundle\"\n--target wasm32-unknown-unknown -- -D warnings` went from **54 errors → 0**. Native\ndefault-features pass also restored to 0 errors. Both targets and the 363 native\nlib tests now pass cleanly under the PostToolUse clippy hook.\n- **Mechanical lints** auto-applied: `sort_by_key` (5×), `clamp` (5×),\n `unwrap_or_default`, `is_some_and`, `manual_abs_diff`,\n `manual_pattern_char_comparison`, `collapsible_match`, `let_and_return`,\n `derivable_impls`, `field_reassign_with_default`, `needless_return`.\n- **Type aliases** for boxed `Fn` benchmark callbacks in\n [graphrag-core/src/monitoring/benchmark.rs:208-214](graphrag-core/src/monitoring/benchmark.rs#L208):\n `RetrievalFn`, `RerankerFn`, `LlmFn`. Eliminates 3× `type_complexity` warnings.\n- **`HierarchicalLeidenResult` type alias** in\n [graphrag-core/src/graph/leiden.rs:17](graphrag-core/src/graph/leiden.rs#L17)\n factored out the `Result<(HashMap<.., HashMap<..>>, HashMap<..>)>` return type\n of `hierarchical_leiden`.\n- **Feature-gated dead-code under wasm**: helper methods in\n `gleaning_extractor.rs`, `llm_extractor.rs`, `chunking_strategies.rs`,\n `contextual_enricher.rs`, `late_chunking.rs` are now `#[cfg(feature = \"async\")]`.\n Fields `ollama_client` (atomic_fact_extractor, llm_extractor), `prompt_builder`\n (llm_extractor), `client` (contextual_enricher), `llm_extractor` (gleaning_extractor),\n `critic` (graphrag/mod), `api_key` (late_chunking), and `boundary_detector` /\n `coherence_scorer` / `min_chunk_chars` (chunking_strategies) carry\n `#[cfg_attr(not(feature = \"async\"), allow(dead_code))]`. Five modules\n carry `#![cfg_attr(not(feature = \"async\"), allow(unused_imports))]` to\n silence imports that become dead when the async build_graph path is gone.\n- **Restored imports lost during refactor**: `TextChunk`, `GraphRAGError`,\n `Document`, `HashMap`, `HashSet`, `Result`, `OllamaGenerationParams` re-added\n to atomic_fact_extractor.rs, gleaning_extractor.rs, llm_extractor.rs,\n contextual_enricher.rs, late_chunking.rs. Underscored-but-still-used\n variables (`_e` → log-formatter args, `_original_score`, `_total_chunks`)\n rewritten to be self-consistent.\n\n### Fixed\n\n#### WASM compilation broken after graphrag-core refactor (2026-05-16)\n`graphrag-core` failed to compile for `wasm32-unknown-unknown` (65 errors → 0). The WASM\nbuild uses `default-features = false` (excludes `async`, `tracing`, `tokio`, `parallel-processing`),\nbut many code paths used `tracing::` calls and `tokio` without feature gates.\n- Added `#[cfg(feature = \"tracing\")]` gates to ~80 `tracing::` calls across 15 files.\n- Gated `tokio::runtime::Runtime` in `BoundaryAwareChunkingStrategy::chunk()` behind\n `#[cfg(feature = \"async\")]` with sync fallback.\n- Split `RetrievalSystem::batch_query()` into `#[cfg(feature = \"parallel-processing\")]`\n and `#[cfg(not(feature = \"parallel-processing\"))` variants.\n- Fixed sync `ask()` (`#[cfg(not(feature = \"async\"))`) to call `retrieval.query()`\n instead of async `query_internal()`.\n- Added `#![recursion_limit = \"512\"]` to `graphrag-wasm` main.rs for Leptos type depth.\n- Created missing `graphrag-wasm/models/` directory required by Trunk.\n\n#### Missing `Relationship` fields in sync `build_graph()` (2026-05-16)\n[graphrag-core/src/graphrag/build.rs:690](graphrag-core/src/graphrag/build.rs#L690):\n`Relationship` struct literal was missing `embedding`, `temporal_type`, `temporal_range`,\nand `causal_strength` fields added in Phase 1.2 (Advanced GraphRAG). Added all four\nwith `None` defaults so the sync build path compiles without partial-init errors.\n\n#### `rograg::validator` dropped quality metrics (2026-05-16)\n[graphrag-core/src/rograg/validator.rs:376](graphrag-core/src/rograg/validator.rs#L376):\n`validate_response` was computing `coherence_score`, `relevance_score`,\n`factual_consistency_score`, `completeness_score`, `readability_score`, and\n`source_credibility_score` then throwing them away (7 unused_variable /\nunused_assignments warnings). Now they:\n- Fold into `validated_response.confidence` via a new `overall_quality()` helper\n (mean of the metrics that were actually run — coherence / relevance / factual\n consistency are gated on their respective config flags; completeness /\n readability / source credibility always count).\n- Trigger a `Medium` `IssueType::Quality` validation issue when overall quality\n falls under 0.5.\n- Are emitted as a structured `tracing::debug!` event so the metrics are\n observable in logs without a public API change.\n\n### Changed\n\n#### Server crate: color-eyre pretty errors at startup (2026-05-16)\n- `graphrag-server/src/main.rs`: `main()` return type `std::io::Result<()>` →\n `color_eyre::Result<()>`, with `color_eyre::install()` at top.\n- Adds `color-eyre = \"0.6\"` to `graphrag-server/Cargo.toml`.\n- mimalloc allocator was already wired (no change).\n- Production unwraps in server crate audited: all 16 remaining unwraps are inside\n `#[cfg(test)]` blocks (qdrant_store, auth, embeddings, config_handler, etc.).\n Production paths use `.map_err(...)?` / `.ok_or_else(...)?` — already clean.\n Part of refactor-2026-05 server slice.\n\n### Documentation\n\n#### Stale memory + CLAUDE.md notes refreshed (2026-05-16)\n- CLAUDE.md workspace layout: 6-crate → 5-crate (graphrag_py removed).\n- CLAUDE.md \"Known gotchas\": replaced obsolete \"12 failing unit tests\" claim with\n verified status: `cargo test -p graphrag-core --lib` → 363 pass / 0 fail. The\n remaining `cargo test --workspace` failures come from stale **examples**\n (not tests) under `graphrag-core/examples/` with missing `Entity` / `Relationship`\n fields; left untouched per project policy.\n- MEMORY.md (auto-memory) synced to the same wording.\n\n### Removed\n\n#### Test suite aggressive pruning (2026-05-16)\nUser-requested clean-up: keep only indispensable, up-to-date tests; delete broken\npre-existing failures, hanging tests, stale pre-refactor integration tests, and\ntrivial construction-only sanity tests.\n\n- **23 broken / hanging / failing unit tests deleted**:\n - `async_graphrag::tests::*` (6 tests on dead module)\n - `entity::*::test_normalize_name` (2 stale assertions)\n - `entity::llm_relationship_extractor::test_fallback_extraction`\n - `reranking::cross_encoder::test_rerank_basic` + `test_confidence_filtering` (need ONNX)\n - `retrieval::symbolic_anchoring::test_extract_anchors` (stale)\n - `text::boundary_detection::test_sentence_detection` + `test_combined_detection`\n - `graph::incremental::tests::test_basic_entity_upsert` + 6 ProductionGraphStore tests\n (deadlock in async lock contention — hung indefinitely)\n - `rograg::logic_form::tests::test_pattern_parser` + `test_logic_form_retrieval`\n - `rograg::intent_classifier::tests::test_{factual,relational,temporal,causal,comparative,summary,definitional}_intent` (7 stale assertions on intent classification)\n - `rograg::quality_metrics::test_performance_stats_update`\n - `rograg::streaming::test_template_selection`\n - `incremental::lazy_propagation::test_lazy_propagation_basic`\n - `incremental::delta_computation::test_parallel_computation`\n- **10 stale workspace-level integration test files deleted** (`./tests/*.rs`,\n all pre-2026, predate the KV cache / GLiNER / persistence / file-split refactors):\n `caching_integration.rs`, `config_integration_test.rs`, `http_endpoint_tests.rs`,\n `hybrid_retrieval_tests.rs`, `integration_tests.rs`, `modular_integration_tests.rs`,\n `property_tests.rs` + `.proptest-regressions`, `server_integration_tests.rs`,\n `zero_cost_approaches_integration_tests.rs`, `tests/parallel/`. Plus\n `graphrag-core/tests/ollama_enhancements.rs` (didn't compile — missing\n `context` field on `OllamaGenerationParams`).\n- **15 trivial `test_*_creation` patterns deleted** (single-line constructions\n verifying only `X::new().is_ok()`): `test_tree_creation`,\n `test_async_mock_llm_creation`, `test_incremental_pagerank_creation`,\n `test_processor_creation`, `test_agent_creation`, `test_function_caller_creation`,\n `test_cache_warmer_creation`, `test_retrieval_system_creation`,\n `test_enhanced_registry_creation`, `test_mock_llm_creation`,\n `test_answer_generator_creation`, `test_graphrag_creation`,\n `test_graph_indexer_creation`, `test_lancedb_creation`,\n `test_cached_client_creation`. Plus 2 trivial Ollama adapter creation tests\n (entire test module in `core/ollama_adapters.rs` removed).\n- **Tests retained**: 7 integration test files in `graphrag-core/tests/` (the\n 2026-02 refactor-era tests exercising KV cache, contextual enricher, GLiNER\n features, triple validation, dynamic weighting, BAR-RAG, text pipeline\n fixtures, incremental graph updates). `./tests/e2e/` benchmark scripts kept.\n- **Verification matrix** — all 100% green:\n - `cargo test -p graphrag-core --lib` → **363 passed, 0 failed** (was 371/12 fail)\n - `cargo test -p graphrag-core --lib --features rograg` → **402 passed, 0 failed**\n - `cargo test -p graphrag-core --lib --features incremental` → **390 passed, 0 failed**\n\n### Fixed\n\n#### Workspace-wide production `unwrap()` sweep (2026-05-16) — Part of refactor-2026-05 Phase 3 (extended)\n- Going beyond the original Phase 3 scope (`voy_store`, `rograg/streaming`,\n `rograg/processor`, `cli/config`, `qdrant_store` — all already verified\n test-only or previously cleaned), every remaining production `.unwrap()` in\n the workspace has been replaced with the appropriate safe alternative.\n- Mechanical sweeps by category:\n - **36 `partial_cmp(...).unwrap()`** (f32 sort comparators, NaN-panic-prone)\n across ~23 files (`async_graphrag`, `inference`, `retrieval/*`, `graph/*`,\n `summarization`, `vector`, `monitoring`, `nlp`, `generation`, server\n handlers, etc.) → `.unwrap_or(std::cmp::Ordering::Equal)`.\n - **22 `lock()`/`read()`/`write().unwrap()`** (Mutex/RwLock acquisitions,\n poisoned-lock-panic-prone) → `.expect(\"lock poisoned\")` /\n `.expect(\"rwlock poisoned\")`.\n - **12 `Regex::new(...).unwrap()`** (static regex literals) →\n `.expect(\"static regex literal\")`.\n - **`duration_since(UNIX_EPOCH).unwrap()`** (system clock) →\n `.expect(\"system clock before UNIX epoch\")`.\n - **Iterator and Option terminators** (`.first()`, `.last()`, `.next()`,\n `.min()`, `.max()`, `.pop()`, `.as_ref()`, `.as_mut()`, `.chars().next()`)\n after checked-precondition usages → `.expect(<reason>)`.\n - **Targeted contextual fixes** for `result_map.remove`, `get_mut` after\n `contains_key`, `Self::new()` in `Default::default`, `NonZeroUsize::new`\n on literal, `caps.get(N)`, `strip_prefix(...)` after `starts_with`, etc.\n- Test-only infrastructure files (`core/test_traits.rs`, `core/test_utils.rs`)\n intentionally left untouched — their `.unwrap()` calls represent test-helper\n panic semantics by design (suite is called from test functions only).\n- **Net result**: workspace audit reports **0 production `.unwrap()` calls**\n outside test infrastructure (down from ~178 pre-existing). All builds\n green: `graphrag-core` default + `--features rograg` + `--features\n incremental`, plus `graphrag-cli`, `graphrag-server`, `graphrag` wrapper.\n\n### Changed\n\n#### Module split: retrieval/types.rs extracted (2026-05-16) — Part of refactor-2026-05 Phase 4 (final)\n- Extracted `RetrievalConfig`, `SearchResult`, `ResultType`, `QueryAnalysis`, `QueryType`,\n `QueryIntent`, `QueryAnalysisResult`, `QueryResult`, `RetrievalStatistics` (+ its\n `print` impl) from `graphrag-core/src/retrieval/mod.rs` into the new private module\n `graphrag-core/src/retrieval/types.rs` (199 LOC).\n- `retrieval/mod.rs` shrinks 1851 → 1666 LOC; the public API is preserved via\n `pub use types::*;` so `crate::retrieval::SearchResult` etc. resolve unchanged.\n- Restored one stripped doc comment (`/// Statistics about the retrieval system`)\n on `RetrievalStatistics` to satisfy `#![warn(missing_docs)]` — the sed extraction\n had eaten the line during slicing.\n- This was the last remaining Phase 4 item from the plan. Build + clippy clean\n (per the `feedback-verify-with-build-clippy` policy).\n\n#### Sub-split: graphrag/ directory module (2026-05-16) — Part of refactor-2026-05 Phase 4\n- Follow-up to the earlier `graphrag.rs` single-file move. The 1753-LOC\n `graphrag-core/src/graphrag.rs` is now a directory module\n `graphrag-core/src/graphrag/` with per-concern sub-files:\n - `mod.rs` (~105 LOC): struct `GraphRAG`, sub-module declarations, private\n `ensure_initialized` helper (bumped `fn` → `pub(super) fn` so the sibling\n `impl` blocks can call it), `#[cfg(test)] mod tests` block with the two\n pre-existing tests.\n - `lifecycle.rs` (~189 LOC): `new`, `default_local`, `builder`, `initialize`,\n `try_load_from_workspace`, `save_to_workspace`, `clear_graph`.\n - `documents.rs` (~53 LOC): `add_document_from_text`, `add_document`.\n - `build.rs` (~715 LOC): async + sync `build_graph` paired methods.\n - `ask.rs` (~519 LOC, renamed from `query.rs` to avoid clash with\n `use crate::query` for the planner module): `ask`, `ask_with_reasoning`,\n `ask_explained`, `query_internal`, `query_internal_with_results`,\n `generate_semantic_answer_from_results`, `remove_thinking_tags`,\n `ask_with_pagerank` pair.\n - `stats.rs` (~85 LOC): `config`, `is_initialized`, `has_documents`,\n `has_graph`, `knowledge_graph`, `knowledge_graph_mut`, `get_entity`,\n `get_entity_relationships`, `get_chunk`.\n - `factory.rs` (~202 LOC): `from_json5_file`, `from_config_file`,\n `from_config_and_document`, `quick_start`, `quick_start_with_config`.\n- Each sub-file has its own `impl GraphRAG { ... }` block; Rust allows multiple\n impl blocks across files. All sub-files share an identical kitchen-sink import\n header (`Config`, core types, `critic`, `ollama`, `persistence`, `query`,\n `retrieval`, feature-gated `parallel`, plus `use super::GraphRAG`).\n- Public API preserved: `graphrag_core::GraphRAG` resolves via `lib.rs`'s\n `pub use graphrag::GraphRAG;` (unchanged from the single-file pass).\n- Verified per the new policy: `cargo build -p graphrag-core` + downstream crates\n green; `cargo clippy -p graphrag-core -- -D warnings` shows exactly one error\n in the new files (`graphrag/ask.rs:408` clamp pattern) which is a verbatim\n carry-over from the previous `graphrag.rs:1358` (originally `lib.rs:1594`) —\n net new errors: zero. Tests not re-run (pure file move; see\n `feedback-verify-with-build-clippy` memory entry).\n\n#### God-file split: graph/incremental/ directory module (2026-05-16) — Part of refactor-2026-05 Phase 4\n- Converted `graphrag-core/src/graph/incremental.rs` (2905 LOC — the biggest god-file in\n the crate) into a directory module `graphrag-core/src/graph/incremental/` with focused\n sub-files:\n - `mod.rs` (~395 LOC): doc + sub-module declarations + `pub use` re-exports + verbatim\n `#[cfg(test)] mod tests` block + the kitchen-sink `use` import block the tests rely\n on via `super::*`.\n - `types.rs` (~465 LOC): `UpdateId`, `TransactionId`, `ChangeRecord`, `ChangeType`,\n `Operation`, `ChangeData`, `Document`, `GraphDelta`, `DeltaStatus`, `RollbackData`,\n `ConflictStrategy`, `Conflict`, `ConflictType`, `ConflictResolution`, the\n `IncrementalGraphStore` trait, `GraphStatistics`, `ConsistencyReport`,\n `InvalidationStrategy`, `CacheRegion`.\n - `helpers.rs` (~496 LOC): `SelectiveInvalidation`, `ConflictResolver`,\n `UpdateMonitor` + impls + their satellite types (`InvalidationStats`, `UpdateMetric`,\n `OperationLog`, `PerformanceStats`).\n - `manager.rs` (~898 LOC): `IncrementalGraphManager` (both feature-gated and non-gated\n paired definitions kept adjacent), `IncrementalConfig`, `IncrementalStatistics`,\n `IncrementalPageRank`, `BatchProcessor`, `PendingBatch`, `BatchMetrics`, plus the\n `impl GraphRAGError` convenience constructors that conceptually belong here.\n - `store.rs` (~743 LOC): `ProductionGraphStore` + `Transaction` + `TransactionStatus`\n + `IsolationLevel` + `ChangeEvent` + `ChangeEventType` + `impl IncrementalGraphStore\n for ProductionGraphStore` + `ChangeDataExt` trait & impl.\n- Public API preserved via `pub use` cascade in `mod.rs` (`crate::graph::incremental::*`\n resolves unchanged).\n- Visibility-only bumps to keep the shared test module compiling across the new\n sub-module boundary:\n - `IncrementalPageRank.scores`: `field` → `pub(super) field`\n - `ConflictResolver.strategy`: `field` → `pub(super) field`\n - `ConflictResolver::merge_entities`: `fn` → `pub(super) fn`\n- Verification strategy update (per user request): switched from `cargo test --features\n incremental` (which surfaces many pre-existing unrelated failures and obscures the\n signal we care about) to `cargo build --features incremental` + `cargo clippy\n --features incremental -- -D warnings`. The clippy run reports 34 errors, all in\n pre-existing files outside the split (`graphrag.rs`, `retrieval/`, `text/`,\n `monitoring/`, etc.); zero new errors in `graph/incremental/`. Downstream crates\n (`graphrag-cli`, `graphrag-server`, `graphrag`) build clean.\n\n#### Module split: config/json_parser.rs extracted (2026-05-16) — Part of refactor-2026-05 Phase 4\n- Extracted `Config::from_file` (~553 LOC hand-rolled JSON reader using the `json`\n crate) and `Config::to_file` (~200 LOC writer) from `graphrag-core/src/config/mod.rs`\n into the new private module `graphrag-core/src/config/json_parser.rs` (769 LOC, with\n imports + `impl Config { ... }` wrapper).\n- `config/mod.rs` shrinks 2491 → 1737 LOC. Public API unchanged: both methods are\n still reachable as `Config::from_file` / `Config::to_file` via the new\n `impl Config` block (multiple impl blocks across files compile fine).\n- Distinct from `config::json5_loader` (serde-based typed JSON5 loader) and\n `config::loader` (multi-format dispatcher) — this is the bespoke `json` crate path.\n- 371 unit tests pass; 12 pre-existing failures unchanged.\n\n#### God-file split: rograg/logic_form/ directory module (2026-05-16) — Part of refactor-2026-05 Phase 4\n- Converted `graphrag-core/src/rograg/logic_form.rs` (1517 LOC) into a directory\n module `graphrag-core/src/rograg/logic_form/` with focused sub-files:\n - `mod.rs` (141 LOC): doc + sub-module declarations + `pub use` re-exports + verbatim `#[cfg(test)] mod tests` block.\n - `types.rs` (333 LOC): `LogicFormError`, `LogicFormQuery`, `Predicate`, `Argument`, `ArgumentType`, `Constraint`, `ConstraintType`, `LogicQueryType`, `LogicFormResult`, `VariableBinding`, `LogicExecutionStats`.\n - `parser.rs` (240 LOC): `LogicFormParser` trait + `PatternBasedParser` + `LogicPattern` + `ArgumentExtractor` + impls.\n - `executor.rs` (673 LOC): `LogicFormExecutor` + impls.\n - `retriever.rs` (217 LOC): `LogicFormRetriever` struct + `Default` + impl.\n- Public API preserved via `pub use` cascade through both `logic_form/mod.rs` and\n `rograg/mod.rs` (`crate::rograg::LogicFormResult`, `crate::rograg::LogicFormRetriever`,\n etc. still resolve unchanged).\n- Single non-mechanical change: bumped `LogicFormExecutor::calculate_name_similarity`\n from private `fn` to `pub(super) fn` — the existing `test_name_similarity` test in\n the shared `tests` module needs cross-submodule access. Visibility-only adjustment;\n no behavior or signature change.\n- Pre-existing test failures (`test_logic_form_retrieval`, `test_pattern_parser`)\n remain unchanged (verified by re-running them on `main` before the split).\n\n#### God-file split: graphrag-core/src/graphrag.rs (2026-05-16) — Part of refactor-2026-05 Phase 4\n- Extracted the `pub struct GraphRAG` and its single `impl GraphRAG { ... }` block\n (constructors, lifecycle, build_graph, ask*, query_internal*, generate_semantic_answer_from_results,\n remove_thinking_tags, getters, factory methods, ensure_initialized, tests) from\n `graphrag-core/src/lib.rs` into the new private module file `graphrag-core/src/graphrag.rs`.\n- `lib.rs` is now a 263-LOC re-export shell (`mod graphrag; pub use graphrag::GraphRAG;`).\n `graphrag.rs` is 1753 LOC (header + verbatim impl + moved `#[cfg(test)] mod tests`).\n- Public API is preserved: `graphrag_core::GraphRAG` and `graphrag_core::prelude::GraphRAG`\n resolve through the new re-export with identical paths.\n- Added module-scoped imports at the top of `graphrag.rs` (`Config`, core types,\n `critic`, `ollama`, `persistence`, `query`, `retrieval`, feature-gated `parallel`)\n so the impl body compiles verbatim without inline path changes.\n- Both moved tests (`test_graphrag_creation`, `test_builder_pattern`) still pass.\n All other pre-existing test/doc failures remain unchanged (12 unit tests, 7 doctests).\n- Sub-splitting the impl across `graphrag/{lifecycle,documents,build,query,stats}.rs`\n remains deferred to a follow-up — single-file move first per plan.\n\n#### Module split: retrieval/explained.rs (2026-05-16) — Part of refactor-2026-05 Phase 4\n- Extracted `ExplainedAnswer`, `SourceReference`, `SourceType`, `ReasoningStep` (and the\n ~160 LOC `impl ExplainedAnswer` block with `from_results` + `format_display`) from\n `graphrag-core/src/retrieval/mod.rs` into new `graphrag-core/src/retrieval/explained.rs`.\n- Public API preserved via `pub use explained::*` in `retrieval/mod.rs` — downstream\n callers see no change.\n- Net effect: `retrieval/mod.rs` shrinks from 2094 LOC → 1851 LOC; new\n `explained.rs` is 250 LOC.\n- Replaced legacy `.min(1.0).max(0.0)` with idiomatic `.clamp(0.0, 1.0)` in the moved\n `from_results` fn (clippy `manual_clamp`).\n- Larger god-file splits (lib.rs 1968 LOC, logic_form.rs 1517, incremental.rs 2905,\n config/mod.rs JSON loader) remain deferred — see plan file.\n\n### Fixed\n\n#### Production unwrap removal (2026-05-16) — Part of refactor-2026-05 Phase 3\n- `rograg/streaming.rs`: regex `unwrap()` → `expect(\"static regex literal\")`; three\n `partial_cmp(...).unwrap()` calls on f32 confidence scores now use\n `unwrap_or(Ordering::Equal)` to avoid panics on NaN.\n- `rograg/processor.rs::RogragProcessorBuilder::build`: replaced inner `.unwrap()` on\n `HybridQueryDecomposer::new()` and `IntentClassifier::new()` with `?` propagation;\n `SystemTime::duration_since(UNIX_EPOCH).unwrap()` → `.expect(\"system clock before\n UNIX epoch\")` (genuine programmer-bug case).\n- `graphrag-server/src/qdrant_store.rs`: removed 6 production `.unwrap()` calls in\n `add_document`, `add_documents_batch`, and `search` — payload `.as_object()`,\n `serde_json::to_value`, `serde_json::from_value`, and `point.id` now propagate\n `QdrantError` via `?` and `Result::collect`.\n- Tests-only `unwrap()` in `vector/voy_store.rs` and `graphrag-cli/src/config.rs`\n left intact (per Phase 3 scope: production paths only).\n\n### Added - GLiNER-Relex Extraction via gline-rs (2026-02-23)\n\n#### GLiNER-Relex Entity + Relation Extractor (`entity/gliner_extractor.rs`, `config/mod.rs`, `config/setconfig.rs`, `lib.rs`)\n- New `GLiNERExtractor`: joint entity + relation extraction in a single forward pass via\n `gline-rs` v1.0.1 + ONNX Runtime. ~1.5 GB VRAM vs 8+ GB for generative LLMs; zero\n structural hallucinations.\n- Two-stage pipeline: NER (SpanPipeline or TokenPipeline) → RE (RelationPipeline), both\n composed on the same `orp::model::Model` with lazy loading via `Arc<RwLock<Option<Model>>>`.\n- Confidence scores propagated natively into `Entity.confidence` and `Relationship.confidence`.\n- Optional feature flag `gliner`: crate compiles and works normally without it.\n- `tokio::task::spawn_blocking` wrapper in `lib.rs` keeps the async runtime unblocked.\n- Config example (JSON5):\n ```json5\n gliner: {\n enabled: true,\n model_path: \"./models/gliner-relex-large-v0.5.onnx\",\n entity_labels: [\"person\", \"organization\", \"location\"],\n relation_labels: [\"controls\", \"located in\", \"causes\"],\n entity_threshold: 0.40,\n relation_threshold: 0.50,\n mode: \"span\", // or \"token\" for gliner-multitask\n use_gpu: false,\n }\n ```\n\n### Added - Graph Persistence / Storage Choice (2026-02-23)\n\n#### Storage Backend — In-Memory vs Disk (`config/mod.rs`, `config/setconfig.rs`, `lib.rs`)\n- `AutoSaveConfig` (and `AutoSaveSetConfig` in SetConfig) now expose:\n - `base_dir: Option<String>` — directory where workspace folders are stored (e.g. `\"./output\"`)\n - `workspace_name: Option<String>` — sub-folder inside `base_dir` (default: `\"default\"`)\n - `enabled: bool` — `false` (default) = in-memory only; `true` = persist to disk\n- `GraphRAG::initialize()` now calls `try_load_from_workspace()`: if `auto_save.enabled = true`\n and the workspace already exists on disk, the graph is **loaded from disk** instead of starting empty.\n The second run reuses the previously built graph automatically.\n- `GraphRAG::save_to_workspace()` — new public method; also called automatically at the end of\n `build_graph()` when persistence is enabled.\n- No-op when `enabled = false`; zero performance cost for in-memory-only deployments.\n- Format hierarchy on disk: Parquet (if `persistent-storage` feature) → JSON fallback (always).\n- JSON5 config usage:\n ```json5\n auto_save: {\n enabled: true,\n base_dir: \"./output\",\n workspace_name: \"my_project\",\n }\n ```\n\n### Fixed - Extraction Temperature (2026-02-23)\n\n#### Zero-Temperature Entity Extraction (`entity/gleaning_extractor.rs`, `entity/llm_extractor.rs`, `config/setconfig.rs`)\n- `GleaningConfig::default()` and `LLMEntityExtractor::new()` now use `temperature: 0.0` (was `0.1`)\n - Fully deterministic JSON output — eliminates spurious token variation that causes parse failures\n - Consistent with recommendations for structured extraction models (NuExtract, Triplex, etc.)\n- `EntityExtractionConfig.temperature` in SetConfig now defaults via `default_extraction_temperature() = 0.0`\n - Separate from `default_temperature() = 0.1` used for general LLM parameters\n - Users can override in JSON5: `entity_extraction.temperature = 0.0`\n- `ContextualEnricher` retains `0.1` (generates natural language descriptions, not strict JSON)\n\n### Fixed & Improved - Entity Extraction, Query Quality & Sources (2026-02-23)\n\n#### SetConfig `use_gleaning` Bug Fix (`config/setconfig.rs`)\n- **Bug**: when `mode.approach = \"semantic\"` with no `semantic:` sub-section, the `else` block\n hardcoded `config.entities.use_gleaning = true` regardless of the top-level `entity_extraction.use_gleaning` field\n- **Fix**: the `else` block now reads from `self.entity_extraction.use_gleaning` and `max_gleaning_rounds` directly\n- This affected ALL JSON5 configs using `mode.approach = \"semantic\"` without an explicit `semantic:` block\n\n#### LLM Single-Pass Entity Extraction (`lib.rs`, `entity/llm_extractor.rs`, `ollama/mod.rs`)\n- New **LLM single-pass path** in `lib.rs`: `ollama.enabled && !use_gleaning` now uses `LLMEntityExtractor`\n instead of falling through to pattern-based regex extraction\n- **Dynamic `num_ctx`** per chunk: `(prompt_tokens + max_output_tokens) × 1.20`, rounded to 1024,\n clamped `[4096, 131072]` — mirrors the `ContextualEnricher` formula\n- `LLMEntityExtractor` now carries `keep_alive: Option<String>` and `with_keep_alive()` builder\n- `call_llm_with_retry` and `call_llm_completion_check` use `generate_with_params` instead of `generate()`\n to pass `num_ctx` and `keep_alive` — activates Ollama KV cache during entity extraction\n- `GleaningEntityExtractor::new` extracts `keep_alive` before consuming the client and threads it through\n- `OllamaClient::config()` getter added for field access without moving\n- **Result on Symposium (274 chunks, mistral-nemo, no gleaning)**: **1,139 entities, 670 relationships**\n (vs 0 relationships previously due to pattern-based fallback)\n\n#### JSON Parse Resilience — Missing `description` Field (`entity/prompts.rs`)\n- `EntityData.description` is now annotated `#[serde(default)]`\n- When the LLM returns JSON with a missing `description` field (e.g. for Project Gutenberg license chunks),\n parsing succeeds with an empty string instead of falling through to the error path and losing all entities\n from that chunk\n- Fixes the `\"JSON repair failed: missing field 'description'\"` errors seen in the last ~10 chunks of\n Project Gutenberg books\n\n#### Multi-Chunk Semantic Answer Generation (`lib.rs`, `handlers/bench.rs`)\n- `generate_semantic_answer_from_results`: reworked context assembly\n - **Removed 400-char truncation**: full chunk content is now passed to the LLM for each result\n - **Deduplication**: tracks seen chunk IDs to avoid repeating the same chunk from multiple entity hits\n - **Relevance sorting**: context sections sorted by score descending before joining\n - **Synthesis prompt**: updated instructions to ask the LLM to synthesize across ALL context sections\n - **Dynamic `num_ctx`**: prompt size calculated at runtime with 20% margin — activates KV cache for answering\n - **`generate_with_params`** used instead of `generate()` — passes `num_ctx`, `keep_alive`, `temperature`\n- `bench.rs`: switched from `graphrag.ask()` to `graphrag.ask_explained()`\n - `sources` in the JSON output now populated with actual chunk IDs and excerpts (was always `[]`)\n\n#### E2E Config — No-Gleaning Mistral Pipeline\n- New config `tests/e2e/configs/kv_no_gleaning_mistral__symposium.json5`\n - `use_gleaning: false`, `keep_alive: \"1h\"`, `chunk_size: 1000`, `chunk_overlap: 200`\n - Uses mistral-nemo:latest for entity extraction and nomic-embed-text for embeddings\n\n### Added - Ollama KV Cache & Contextual Retrieval (2026-02-22)\n\n#### Ollama KV Cache Parameters (`ollama/mod.rs`, `config/mod.rs`, `config/setconfig.rs`)\n- **`keep_alive`** field added to `OllamaConfig` and `OllamaGenerationParams`\n - Keeps the Ollama model loaded in VRAM between requests (prevents KV cache eviction)\n - Critical for multi-chunk document processing: without it, the model unloads between each chunk\n - Default: `None` (uses Ollama's built-in 5-minute default)\n - Example: `\"1h\"` for book-length document processing sessions\n- **`num_ctx`** field added to `OllamaConfig` and `OllamaGenerationParams`\n - Explicitly sets the context window size (Ollama silently truncates to 2k-8k without this)\n - Goes into the `options` object in Ollama API requests; `keep_alive` is a top-level field\n - Default: `None` (uses Ollama's default, usually 2048-8192 tokens)\n - Example: `32768` for documents up to ~130k characters\n- Both fields wired through the full config stack: JSON5 parser, `OllamaSetConfig`, request body\n\n#### Contextual Chunk Enricher (`text/contextual_enricher.rs`)\n- New module implementing [Anthropic's Contextual Retrieval](https://www.anthropic.com/news/contextual-retrieval) pattern\n- `ContextualEnricher`: augments each chunk with 2-3 sentences of document-level context before embedding\n- **KV Cache optimization**: static prefix (full document) is cached by Ollama; only the chunk suffix is re-evaluated per request\n - First chunk: ~2 min (loads document into KV cache on RTX 4070 with Mistral-NeMo 12B)\n - Subsequent chunks: ~3-5 sec each (only chunk tokens evaluated)\n - ~100 chunks from a 45k-token book: **5-10 minutes total** vs hours without KV cache\n- `calculate_num_ctx()`: dynamic context window calculation per document\n - Formula: `tokens(instructions) + tokens(document) + tokens(largest_chunk) + output_budget + 5% margin`\n - Rounded to nearest 1024, clamped to `[4096, 131072]`\n- `enrich_document_chunks()` and `enrich_chunks()`: async, groups chunks by source document\n- Output format: `[LLM context]\\n\\n[original chunk text]` — preserves original text verbatim\n\n#### Late Chunking Strategy (`text/late_chunking.rs`)\n- New `LateChunkingStrategy` implementing `ChunkingStrategy` trait (Jina AI technique)\n- Produces chunks annotated with `position_in_document` metadata (byte spans) for post-hoc pooling\n- `JinaLateChunkingClient`: calls Jina Embeddings API v2 with `late_chunking: true`\n- `split_into_sections()`: handles documents exceeding model context window (8192 tokens for Jina v3)\n- `LateChunkingConfig`: configurable chunk size, overlap, max document tokens, position annotation\n\n#### E2E Benchmark KV Cache Support (`tests/e2e/run_benchmarks.sh`)\n- Three new pipeline dimensions: `keep_alive`, `num_ctx`, `ollama_timeout`\n- All existing pipelines updated with explicit defaults (`keep_alive=none`, `num_ctx=0`)\n- Semantic/hybrid pipelines with Ollama now default to `keep_alive=30m` (model stays loaded during build phase)\n- **Three new KV cache pipelines** targeting long document processing:\n - `kv_semantic_mistral`: semantic approach, Mistral-NeMo, `keep_alive=1h`, `num_ctx=32768`, timeout=300s\n - `kv_hybrid_mistral`: hybrid approach, Mistral-NeMo, `keep_alive=1h`, `num_ctx=32768`, timeout=300s\n - `kv_semantic_qwen3`: semantic approach, Qwen3 8B Q4, `keep_alive=1h`, `num_ctx=16384`, timeout=300s\n- KV Cache settings shown in run header when active\n- Generated JSON5 configs include `keep_alive` and `num_ctx` in the `ollama` section\n\n#### Tests\n- `tests/contextual_enricher_e2e.rs`: 4 tests for `ContextualEnricher`\n - `test_enriched_chunk_contains_original_and_context` (`#[ignore]`, requires `ENABLE_OLLAMA_TESTS=1`)\n - `test_kv_cache_speedup` (`#[ignore]`) — measures per-chunk timing and speedup ratio\n - `test_num_ctx_calculation_sanity` — always-run, validates num_ctx formula bounds\n - `test_disabled_enricher_returns_chunks_unchanged` — always-run no-op safety check\n\n### Added - Service Registry Completion (2025-02-11)\n\n#### Core Infrastructure\n- **Complete test utilities module** (`core/test_utils.rs`):\n - `MockEmbedder`: Deterministic hash-based embedding generation with dimension support\n - `MockLanguageModel`: Configurable response mapping for testing\n - `MockVectorStore`: In-memory vector store with cosine similarity search\n - `MockRetriever`: Simple retriever for testing search pipelines\n - All mocks fully implement core `Async*` traits\n - 100% test coverage with 5 passing test cases\n\n#### Adapter Implementations\n- **Entity extraction adapter** (`core/entity_adapters.rs`):\n - `GraphIndexerAdapter` bridges LightRAG's GraphIndexer to `AsyncEntityExtractor` trait\n - Configurable confidence threshold filtering\n - Entity type conversion from domain-specific to core types\n - Batch extraction support\n - Feature-gated with `lightrag` feature\n\n- **Retrieval system adapter** (`core/retrieval_adapters.rs`):\n - `RetrievalSystemAdapter` implements `AsyncRetriever` trait\n - Integration with KnowledgeGraph-based retrieval\n - Batch search support\n - Comprehensive documentation on graph requirements\n - Feature-gated with `basic-retrieval` feature\n\n- **Metrics collector implementation** (`monitoring/metrics_collector.rs`):\n - Thread-safe metrics with DashMap for counters, gauges, and histograms\n - Atomic operations for zero-lock contention\n - Histogram statistics: count, sum, mean, min, max, p50, p95, p99\n - Timer support with start/finish API\n - Metric tagging with key-value pairs\n - 7/7 passing tests for all metric types\n - Feature-gated with `dashmap` and `monitoring` features\n\n#### Registry Integration\n- **Service registration in `ServiceConfig::build_registry()`**:\n - Entity extractor registration (with `lightrag` feature)\n - Retriever registration (with `basic-retrieval` feature)\n - Metrics collector registration (with `dashmap` + `monitoring` features)\n - Mock services for testing via `with_test_defaults()`\n - Proper feature-gating for modular compilation\n\n#### Documentation\n- **Architectural documentation**:\n - Documented trait hierarchy for vector stores (domain-specific vs generic)\n - Explained when to use adapters vs direct implementations\n - Clarified graph integration requirements for retrieval\n - Added TODO markers for future unification work\n - Inline examples in all adapter modules\n\n- **Code quality improvements**:\n - Removed unused imports across multiple modules\n - Fixed parameter name warnings in data import\n - Commented out incomplete vector-memory feature gate\n - Clean compilation with `async,ollama,dashmap,monitoring,basic-retrieval,lightrag` features\n\n#### Testing\n- **310 tests passing** in graphrag-core library\n- All new service implementations verified:\n - `test_mock_embedder`: Hash-based deterministic embeddings\n - `test_mock_language_model`: Response mapping\n - `test_mock_vector_store`: Cosine similarity search\n - `test_mock_retriever`: Basic search operations\n - Metrics collector tests: counters, gauges, histograms, timers\n- Integration tests for service registration and retrieval\n\n### Added - Ollama Advanced Integration (2025-02-11)\n\n#### Streaming Support\n- **Real-time token generation** with tokio channel-based streaming\n- `generate_streaming()` method returns `tokio::sync::mpsc::Receiver<String>`\n- Server-Sent Events (SSE) parsing for Ollama streaming API\n- Background task spawning for non-blocking stream reads\n- Automatic statistics recording for streamed responses\n- Example usage in test suite (`tests/ollama_enhancements.rs`)\n\n#### Custom Generation Parameters\n- **`OllamaGenerationParams` struct** for fine-grained control:\n - `num_predict`: Maximum tokens to generate\n - `temperature`: Sampling temperature (0.0 - 1.0)\n - `top_p`: Nucleus sampling threshold\n - `top_k`: Top-k sampling\n - `stop`: Stop sequences (array of strings)\n - `repeat_penalty`: Repetition control\n- `generate_with_params()` method for custom parameter usage\n- Integration with `AsyncLanguageModel` trait's `complete_with_params()`\n- Automatic conversion between core and Ollama parameter formats\n\n#### Model Response Caching\n- **DashMap-based caching** for thread-safe concurrent access\n- Automatic cache population on API responses\n- Cache hit detection before making API calls\n- Performance: <1ms for cache hits vs 100-1000ms for API calls\n- Cache management API:\n - `clear_cache()`: Clear all cached responses\n - `cache_size()`: Get number of cached items\n- Configurable via `OllamaConfig.enable_caching` (default: `true`)\n- 80%+ hit rate on repeated queries\n- 6x cost reduction potential\n\n#### Metrics & Usage Tracking\n- **`OllamaUsageStats` struct** with atomic counters:\n - `total_requests`: Total number of API calls\n - `successful_requests`: Successful completions\n - `failed_requests`: Failed attempts\n - `total_tokens`: Cumulative token count (estimated)\n- Thread-safe atomic operations (`Arc<AtomicU64>`)\n- Zero lock contention for metrics updates\n- API methods:\n - `record_success(tokens)`: Record successful request\n - `record_failure()`: Record failed request\n - `get_success_rate()`: Calculate success percentage (0.0 - 1.0)\n- Integration with `AsyncLanguageModel::get_usage_stats()`\n- Automatic token estimation (~4 characters per token)\n\n#### Service Registry Integration\n- **Type-safe service injection** for Ollama services\n- `OllamaEmbedderAdapter` implements `AsyncEmbedder` trait\n- `OllamaLanguageModelAdapter` implements `AsyncLanguageModel` trait\n- Automatic registration in `ServiceConfig::build_registry()`\n- Support for both embeddings and language model services\n- `MemoryVectorStore` registration for in-memory operations\n\n#### Documentation\n- **Complete OLLAMA_INTEGRATION.md guide** with:\n - Setup and prerequisites\n - Basic and advanced usage examples\n - Supported models (embeddings and LLM)\n - Configuration options reference\n - Batch processing examples\n - Custom parameter examples\n - Performance tips and troubleshooting\n- Updated `graphrag-core/README.md` with new features\n- Updated main `README.md` with Ollama integration section\n- API reference with code examples\n- Sources and external documentation links\n\n#### Testing\n- **8 new test cases** in `tests/ollama_enhancements.rs`:\n - Config with caching test\n - Custom generation parameters test\n - Client statistics API test\n - Stats recording test\n - Cache management test\n - Default parameters test\n - Adapter integration tests\n- All tests passing (13/13 total including registry tests)\n- Compilation verified with all feature combinations\n\n#### Configuration Updates\n- Added `enable_caching: bool` to `OllamaConfig`\n- Updated all `OllamaConfig` initializers across codebase:\n - `config/mod.rs`: TOML parsing\n - `config/setconfig.rs`: Config mapping\n - `entity/llm_relationship_extractor.rs`: LLM extraction\n- Default caching: enabled (`true`)\n\n### Changed\n\n- **Model info updated**: `supports_streaming` now returns `true`\n- **AsyncLanguageModel implementation**: Now uses `generate_with_params()` internally\n- **OllamaClient structure**: Added `stats` and `cache` fields\n- **Error handling**: Improved with metrics recording on failures\n- **Test count**: Increased from 214+ to 220+ test cases\n\n### Fixed\n\n- Missing `enable_caching` field in `OllamaConfig` initializers\n- Incorrect `ModelUsageStats` field mapping in adapter\n- Iterator reference error in execute_caused_query\n- Compilation warnings for unused imports\n\n## [0.1.1] - Previous Release\n\n### Added - Core GraphRAG Implementation\n- Temporal and causal reasoning for RoGRAG\n- Graph indexer with 23 relationship patterns\n- Service registry pattern for dependency injection\n- GraphRAGBuilder with fluent API\n- Parquet persistence for entities, relationships, documents\n- Memory vector store implementation\n- Complete trait-based architecture\n\n### Added - Research Features\n- LightRAG dual-level retrieval (6000x token reduction)\n- Leiden community detection (+15% modularity)\n- Cross-encoder reranking (+20% accuracy)\n- HippoRAG personalized PageRank (10-30x cost reduction)\n- Semantic chunking with better boundaries\n\n### Added - Infrastructure\n- Comprehensive test suite (214+ tests)\n- Production-grade logging with tracing\n- Feature flags for modular compilation\n- WASM support with WebGPU acceleration\n- Docker Compose deployment\n\n## [0.1.0] - Initial Release\n\n### Added\n- Basic GraphRAG pipeline\n- Entity and relationship extraction\n- Vector embeddings support\n- Graph construction and querying\n- REST API server\n- CLI tools\n\n---\n\n## Migration Guides\n\n### Upgrading to Ollama Advanced Features\n\nIf you're using basic Ollama integration, upgrading to the new features is seamless:\n\n**Before** (still works):\n```rust\nlet client = OllamaClient::new(OllamaConfig::default());\nlet response = client.generate(\"Hello\").await?;\n```\n\n**After** (with new features):\n```rust\nlet config = OllamaConfig {\n enable_caching: true, // NEW: Enable caching\n ..Default::default()\n};\nlet client = OllamaClient::new(config);\n\n// Streaming\nlet mut rx = client.generate_streaming(\"Hello\").await?;\nwhile let Some(token) = rx.recv().await {\n print!(\"{}\", token);\n}\n\n// Custom parameters\nlet params = OllamaGenerationParams {\n temperature: Some(0.8),\n top_p: Some(0.95),\n ..Default::default()\n};\nlet response = client.generate_with_params(\"Hello\", params).await?;\n\n// Metrics\nlet stats = client.get_stats();\nprintln!(\"Success rate: {:.2}%\", stats.get_success_rate() * 100.0);\n```\n\n### Breaking Changes\n\nNone! All new features are opt-in and backward compatible.\n\n---\n\n## Development\n\n### Building from Source\n```bash\ngit clone https://github.com/your-username/graphrag-rs.git\ncd graphrag-rs\ncargo build --release --features async,ollama,dashmap\n```\n\n### Running Tests\n```bash\ncargo test --all-features\ncargo test -p graphrag-core --test ollama_enhancements\n```\n\n### Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n---\n\n**For complete documentation**, see:\n- [README.md](README.md) - Main project documentation\n- [graphrag-core/OLLAMA_INTEGRATION.md](graphrag-core/OLLAMA_INTEGRATION.md) - Ollama guide\n- [graphrag-core/README.md](graphrag-core/README.md) - Core library docs\n- [ARCHITECTURE.md](ARCHITECTURE.md) - System architecture\n"}}}}