1 line
67 KiB
JSON
1 line
67 KiB
JSON
{"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"}}}} |