okfgraph 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. okfgraph-0.2.4/LICENSE +6 -0
  2. okfgraph-0.2.4/PKG-INFO +25 -0
  3. okfgraph-0.2.4/README.md +327 -0
  4. okfgraph-0.2.4/okfgraph/__init__.py +7 -0
  5. okfgraph-0.2.4/okfgraph/cli.py +1364 -0
  6. okfgraph-0.2.4/okfgraph/components/__init__.py +61 -0
  7. okfgraph-0.2.4/okfgraph/components/converters.py +128 -0
  8. okfgraph-0.2.4/okfgraph/components/delta.py +271 -0
  9. okfgraph-0.2.4/okfgraph/components/diff.py +172 -0
  10. okfgraph-0.2.4/okfgraph/components/doctor.py +216 -0
  11. okfgraph-0.2.4/okfgraph/components/embedding.py +323 -0
  12. okfgraph-0.2.4/okfgraph/components/export.py +354 -0
  13. okfgraph-0.2.4/okfgraph/components/image_assets.py +319 -0
  14. okfgraph-0.2.4/okfgraph/components/import_.py +1110 -0
  15. okfgraph-0.2.4/okfgraph/components/ingest.py +495 -0
  16. okfgraph-0.2.4/okfgraph/components/links.py +133 -0
  17. okfgraph-0.2.4/okfgraph/components/lint.py +118 -0
  18. okfgraph-0.2.4/okfgraph/components/purge.py +342 -0
  19. okfgraph-0.2.4/okfgraph/components/ranking.py +168 -0
  20. okfgraph-0.2.4/okfgraph/components/schema.py +443 -0
  21. okfgraph-0.2.4/okfgraph/components/search.py +1006 -0
  22. okfgraph-0.2.4/okfgraph/config.py +393 -0
  23. okfgraph-0.2.4/okfgraph/images.py +435 -0
  24. okfgraph-0.2.4/okfgraph/mcp_server.py +461 -0
  25. okfgraph-0.2.4/okfgraph/models.py +128 -0
  26. okfgraph-0.2.4/okfgraph/router.py +485 -0
  27. okfgraph-0.2.4/okfgraph/security.py +269 -0
  28. okfgraph-0.2.4/okfgraph/tools.py +460 -0
  29. okfgraph-0.2.4/okfgraph.egg-info/PKG-INFO +25 -0
  30. okfgraph-0.2.4/okfgraph.egg-info/SOURCES.txt +70 -0
  31. okfgraph-0.2.4/okfgraph.egg-info/dependency_links.txt +1 -0
  32. okfgraph-0.2.4/okfgraph.egg-info/entry_points.txt +3 -0
  33. okfgraph-0.2.4/okfgraph.egg-info/requires.txt +20 -0
  34. okfgraph-0.2.4/okfgraph.egg-info/top_level.txt +1 -0
  35. okfgraph-0.2.4/pyproject.toml +57 -0
  36. okfgraph-0.2.4/setup.cfg +4 -0
  37. okfgraph-0.2.4/tests/test_chunk_search.py +115 -0
  38. okfgraph-0.2.4/tests/test_chunking.py +533 -0
  39. okfgraph-0.2.4/tests/test_cli.py +186 -0
  40. okfgraph-0.2.4/tests/test_config.py +154 -0
  41. okfgraph-0.2.4/tests/test_converter.py +131 -0
  42. okfgraph-0.2.4/tests/test_delta.py +392 -0
  43. okfgraph-0.2.4/tests/test_diff.py +90 -0
  44. okfgraph-0.2.4/tests/test_directory_hash.py +240 -0
  45. okfgraph-0.2.4/tests/test_doctor.py +93 -0
  46. okfgraph-0.2.4/tests/test_export_compliance.py +171 -0
  47. okfgraph-0.2.4/tests/test_gpu_integration.py +482 -0
  48. okfgraph-0.2.4/tests/test_graph_enrichment.py +153 -0
  49. okfgraph-0.2.4/tests/test_images.py +209 -0
  50. okfgraph-0.2.4/tests/test_ingest.py +380 -0
  51. okfgraph-0.2.4/tests/test_ingest_tool.py +134 -0
  52. okfgraph-0.2.4/tests/test_integration.py +149 -0
  53. okfgraph-0.2.4/tests/test_lint.py +138 -0
  54. okfgraph-0.2.4/tests/test_logging.py +150 -0
  55. okfgraph-0.2.4/tests/test_mcp_server.py +458 -0
  56. okfgraph-0.2.4/tests/test_models.py +137 -0
  57. okfgraph-0.2.4/tests/test_obsidian.py +159 -0
  58. okfgraph-0.2.4/tests/test_okf_ingest_tool.py +199 -0
  59. okfgraph-0.2.4/tests/test_parity.py +140 -0
  60. okfgraph-0.2.4/tests/test_pdf_e2e.py +183 -0
  61. okfgraph-0.2.4/tests/test_ppr_search.py +134 -0
  62. okfgraph-0.2.4/tests/test_ranking.py +101 -0
  63. okfgraph-0.2.4/tests/test_reconstruction.py +106 -0
  64. okfgraph-0.2.4/tests/test_reserved.py +128 -0
  65. okfgraph-0.2.4/tests/test_roundup_cli.py +139 -0
  66. okfgraph-0.2.4/tests/test_router.py +422 -0
  67. okfgraph-0.2.4/tests/test_router_misc.py +544 -0
  68. okfgraph-0.2.4/tests/test_rust_backend.py +76 -0
  69. okfgraph-0.2.4/tests/test_rust_e2e.py +95 -0
  70. okfgraph-0.2.4/tests/test_sanitize.py +96 -0
  71. okfgraph-0.2.4/tests/test_search_browser.py +206 -0
  72. okfgraph-0.2.4/tests/test_security.py +198 -0
okfgraph-0.2.4/LICENSE ADDED
@@ -0,0 +1,6 @@
1
+ This project is dual-licensed under the terms of either:
2
+
3
+ - MIT License (see [LICENSES/MIT.txt](LICENSES/MIT.txt))
4
+ - Apache License, Version 2.0 (see [LICENSES/Apache-2.0.txt](LICENSES/Apache-2.0.txt))
5
+
6
+ You may choose either license for your use.
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: okfgraph
3
+ Version: 0.2.4
4
+ Summary: Ladybug-backed OKF knowledge graph with ONNX + Jina v5 embeddings
5
+ License-Expression: Apache-2.0 OR MIT
6
+ Requires-Python: >=3.11
7
+ License-File: LICENSE
8
+ Requires-Dist: ladybug==0.20.3
9
+ Requires-Dist: okf-embed>=0.1
10
+ Requires-Dist: pydantic>=2.0
11
+ Requires-Dist: python-frontmatter>=1.0
12
+ Requires-Dist: pyyaml>=6.0
13
+ Requires-Dist: numpy>=1.26
14
+ Requires-Dist: onnxruntime==1.29.0
15
+ Requires-Dist: mordant>=0.9
16
+ Requires-Dist: mcp>=2.0
17
+ Requires-Dist: fasteners>=0.19
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=8.0; extra == "dev"
20
+ Provides-Extra: pdf
21
+ Requires-Dist: bobine>=0.5; extra == "pdf"
22
+ Provides-Extra: omni
23
+ Requires-Dist: sentence-transformers>=3.0; extra == "omni"
24
+ Requires-Dist: Pillow>=10.0; extra == "omni"
25
+ Dynamic: license-file
@@ -0,0 +1,327 @@
1
+ # OKFgraph 0.2.4
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/okfgraph)](https://pypi.org/project/okfgraph/)
4
+ [![Python](https://img.shields.io/badge/python-%3E%3D3.11-blue)](https://www.python.org/)
5
+ [![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-green)](LICENSE)
6
+ [![MCP](https://img.shields.io/badge/MCP-%E2%89%A52.0-purple)](https://modelcontextprotocol.io/)
7
+ [![Ladybug](https://img.shields.io/badge/ladybug-0.20.3-orange)](https://pypi.org/project/ladybug/)
8
+
9
+ **Ladybug-backed knowledge graph with Rust-driven Jina v5 embeddings, model-free
10
+ graph retrieval, and agent-first MCP + CLI surfaces.**
11
+
12
+ OKFgraph is a Python library, CLI (`okf`), and MCP server (`okf-mcp`) for
13
+ building, querying, and maintaining a knowledge graph from Markdown/OKF
14
+ documents. It combines hybrid semantic search (vector + FTS, RRF-fused),
15
+ chunk-level retrieval, **model-free PPR retrieval that works with no embedding
16
+ model loaded**, structural diffing, scored health checks, and Obsidian-vault
17
+ compatible wikilinks — in a single LadybugDB file.
18
+
19
+ Design stance: **slim dependencies, no legacy fallbacks.** Embeddings run
20
+ through the `okf-embed` Rust wheel (ONNX Runtime, no torch / transformers /
21
+ optimum anywhere). PDF conversion runs through the `bobine` Rust engine behind
22
+ a swappable `DocumentConverter` seam. What isn't needed isn't installed.
23
+
24
+ ---
25
+
26
+ ## Features
27
+
28
+ | Category | Features |
29
+ |---|---|
30
+ | **Embeddings** | Jina v5 (`jina-embeddings-v5-text-small-retrieval`) via the `okf-embed` Rust wheel; last-token pooling, Matryoshka truncation (32–1024, default 512); omni model (`…-omni-small-retrieval`) lazy-loaded for images only |
31
+ | **Search** | Hybrid RRF fusion (vector + FTS) at concept and chunk granularity; `rank=none\|hub\|ppr` — including **PPR**: lexical seeds → exact Personalized PageRank, zero model load, deterministic |
32
+ | **Read** | Body / chunks / rebuilt document / graph context, with optional **token budgets** (`max_tokens`): self first, then PPR-ranked neighbours, index-first for context |
33
+ | **Storage** | LadybugDB `==0.20.3` (pinned — newer 0.20.x segfaults index builds): graph + vector + FTS in one file |
34
+ | **Links** | Path links (`](doc.md)`) + `[[wikilinks]]` resolved by name (`uid` → `aliases` → `title` → filename stem); ambiguous names never resolve; broken links tracked and repairable |
35
+ | **Import** | Single files, whole bundles (delta-aware: only changed files re-embed, `--purge` drops deleted concepts), markdown / PDF / raw thoughts; mordant lint on the way in |
36
+ | **PDF** | `DocumentConverter` seam with `BobineConverter` default (routing `auto\|surgical\|always\|never`); bring your own converter, no code changes |
37
+ | **Diff** | `okf diff`: structural snapshot (dir vs dir, no model load) and drift (graph vs dir) modes; CI exit codes + `--json` |
38
+ | **Doctor** | `okf doctor`: 0–100 health score (broken/orphan/stale/duplicate-title/missing-description), safe `--fix` that never touches `reviewed: true`, `--strict` CI gate |
39
+ | **Export** | OKF round-trip with See Also / Cited By enrichment + index files, or `--flavor obsidian` (`[[Title]]` wikilinks, no index files, edge-lossless re-import) |
40
+ | **Images** | Modes `text` / `optional` / `omni`, shared text+image vector space, content-hash dedup, `okf-asset://` protocol |
41
+ | **MCP** | 5 tools (`search`, `read`, `traverse`, `ingest`, `export_bundle`), MCP ≥ 2.0 (`MCPServer` + `ToolAnnotations`), stdio transport |
42
+ | **CLI** | Same 5 verbs plus maintenance (`init`, `import`, `diff`, `doctor`, `shell`, `reindex`, `broken-links`, `deleted-*`, …), slim per-command help, `okfgraph.toml` config |
43
+ | **Skills** | 3 harness-neutral skills (`okfgraph-mcp`, `okfgraph-cli`, `okfgraph-ingest`), `.mcp.json` wiring included |
44
+
45
+ ---
46
+
47
+ ## Installation
48
+
49
+ Requires Python ≥ 3.11.
50
+
51
+ ```bash
52
+ pip install "okfgraph[pdf,omni]" # PyPI release (okf-embed ships platform wheels)
53
+ ```
54
+
55
+ Or from source with `uv` (also builds the `okf-embed` Rust wheel from
56
+ `rust/okf-embed` via `[tool.uv.sources]`):
57
+
58
+ ```bash
59
+ git clone <repo> && cd OKFgraph
60
+ uv sync # core: ladybug, okf-embed, onnxruntime, mordant, mcp, …
61
+ uv sync --extra pdf # bobine PDF converter
62
+ uv sync --extra omni # sentence-transformers + Pillow (image embeddings)
63
+ uv sync --extra dev # pytest
64
+ ```
65
+
66
+ Core dependencies are deliberately few: `ladybug==0.20.3`, `okf-embed`,
67
+ `onnxruntime==1.29.0` (one pinned ORT binary shared by bobine + okf-embed;
68
+ `ORT_DYLIB_PATH`-overridable), `mordant`, `mcp>=2.0`, `pydantic`, `pyyaml`,
69
+ `numpy`, `python-frontmatter`, `fasteners`. There is no torch, no
70
+ transformers, no optimum, no PDF stack in the core install.
71
+
72
+ ---
73
+
74
+ ## Quick start
75
+
76
+ ```bash
77
+ okf init --db kb.db --bundle kb/ # once; persists to okfgraph.toml
78
+ okf import --all --bundle kb/ # delta-aware bulk import
79
+ okf search "honey badger defense" # hybrid semantic search
80
+ okf search --rank ppr "honey badger" # same question, no model load
81
+ okf read savanna --include context --max-tokens 1500
82
+ okf traverse savanna --relationship LINKS_TO --direction BOTH
83
+ okf diff # drift: graph vs bundle dir
84
+ okf doctor # health score + findings
85
+ okf lint kb/ # pre-import gate: frontmatter + links, no model
86
+ ```
87
+
88
+ ### Programmatic usage
89
+
90
+ ```python
91
+ from pathlib import Path
92
+ from okfgraph import OKFRouter
93
+
94
+ router = OKFRouter(db_path="kb.db", bundle_root="kb/", embedding_dim=512)
95
+
96
+ # Import: whole bundle (delta-aware), one file, or raw reasoning
97
+ ids = router.import_mgr.import_bundle(Path("kb/"), purge_deleted=False)
98
+ cid = router.import_from_okf(Path("kb/auth.md"), mode="text")
99
+ th = router.ingest_mgr.ingest_thoughts("Session decided X because Y", topic="auth-refactor")
100
+
101
+ # Search: hybrid (default), hub-blended, or model-free PPR
102
+ hits = router.search_hybrid("session auth decisions", limit=5)
103
+ hubbed = router.search_hybrid("session auth decisions", rank="hub")
104
+ cold = router.search_engine.search_with_ppr("auth refactor") # no ONNX load
105
+
106
+ # Read: full shapes, or a token-budgeted section list
107
+ doc = router.get_by_id("auth")
108
+ reading = router.search_engine.read_with_budget("auth", include="context", max_tokens=1500)
109
+
110
+ # Maintain: drift, health, links
111
+ print(router.diff_db_dir(Path("kb/"))["identical"]) # True when in sync
112
+ print(router.diagnose()["score"]) # 0-100
113
+ print(router.doctor_fix()) # safe repairs only
114
+ print(router.repair_links()) # re-point broken links
115
+
116
+ # Export: OKF bundle or Obsidian vault
117
+ router.export_mgr.export_bundle(Path("out-okf/"))
118
+ router.export_mgr.export_bundle(Path("out-vault/"), flavor="obsidian")
119
+
120
+ router.close()
121
+ ```
122
+
123
+ ### Ingestion kinds
124
+
125
+ | Kind | Entry point | Notes |
126
+ |---|---|---|
127
+ | `md` | `ingest_md(md_path, concept_id?, title?, tags?, mode?)` | mordant-linted; frontmatter wins over overrides |
128
+ | `pdf` | `ingest_pdf(pdf_path, auto_import?, routing_mode?, …)` | bobine converter; `md_path` is transient when auto-importing — verify by searching, not by reading the path |
129
+ | `thoughts` | `ingest_thoughts(text, topic, tags?)` | cheapest, highest value: persist session reasoning with a stable topic scheme (`auth-refactor`, `api-design`) |
130
+
131
+ Frontmatter that matters: `title`, `type`, `tags`, `aliases: [...]` (wikilink
132
+ names), `id:` (stable identity, preserved as `uid`, written back on export),
133
+ `reviewed: true` (immune to `doctor --fix`). Credentials in `resource:`
134
+ URIs are stripped to `***@` at parse time (graphs imported before 0.2.3
135
+ keep old values until re-import). Rule of thumb: thoughts > md >
136
+ pdf — reasoning you already hold beats re-extracting it from files.
137
+
138
+ ---
139
+
140
+ ## CLI reference
141
+
142
+ Five verbs mirror the MCP tools; maintenance commands cover the rest. Global
143
+ flags (`--db`, `--bundle`, `--dim`, …) are documented once in `okf --help`,
144
+ accepted everywhere, and usually live in `okfgraph.toml`.
145
+
146
+ | Command | Description |
147
+ |---|---|
148
+ | `okf search QUERY [--target concepts\|chunks\|images] [--rank none\|hub\|ppr] [--expand] [--hub-rerank]` | Concepts (RRF hybrid), chunks (passages), images; `--rank ppr` is model-free |
149
+ | `okf read ID [--include body\|chunks\|document\|context] [--max-tokens N]` | Full shapes uncapped; budgeted section list with `--max-tokens` |
150
+ | `okf traverse [ID] [--relationship CONTAINS\|LINKS_TO\|PART_OF\|INCLUDES_ASSET] [--direction …] [--target ID]` | Relationships, directory listing (empty ID = root), shortest path via `--target` |
151
+ | `okf ingest --kind md\|pdf\|thoughts …` | `--md-file`, `--pdf-file` (`--routing-mode`, `--auto-import`), `--thoughts --topic` |
152
+ | `okf export --all\|--concept-id ID --output DIR [--flavor okf\|obsidian]` | Bundle export; obsidian = `[[Title]]` links, no index files |
153
+ | `okf diff [OLD] [NEW] [--json]` | Snapshot (two dirs, no model) or drift (graph vs dir); exit 0 identical / 1 different |
154
+ | `okf lint [DIR] [--json]` | Pre-import gate (no DB, no model); exit 0 clean / 1 errors / 2 bad dir |
155
+ | `okf doctor [--fix] [--strict] [--stale-days N] [--json]` | Score + findings; `--fix` repairs safely, `--strict` exits 1 on any finding |
156
+ | `okf import [--all] [--purge] [--mode text\|optional\|omni]` | Bulk/single import, delta-aware |
157
+ | `okf init`, `okf model-info`, `okf shell`, `okf reindex`, `okf broken-links`, `okf repair-links`, `okf deleted-*` | Setup, cache inspection, REPL, index rebuild, link + soft-delete maintenance |
158
+
159
+ Every CLI call cold-boots the router (model load ~30s when the embedder is
160
+ needed) — batch reads, and reach for `--rank ppr` for topic queries in cold
161
+ sessions.
162
+
163
+ ---
164
+
165
+ ## MCP server
166
+
167
+ 5 tools, MCP ≥ 2.0, stdio transport. Wire once per project with a **stable**
168
+ `--db-path` + `--bundle` (a temp dir means amnesia every session); first boot
169
+ creates the schema; pre-approve the `ingest` / `export_bundle` write tools for
170
+ knowledge workflows. `.mcp.json` ships a ready config (`uv run --project .
171
+ okf-mcp`).
172
+
173
+ ```bash
174
+ okf-mcp --db-path ./kb.db --bundle-root ./kb --embedding-dim 1024
175
+ ```
176
+
177
+ | Tool | Routing |
178
+ |---|---|
179
+ | `search(query, target?, rank?, …)` | `concepts` = open questions; `chunks` = exact passages (`expand`, `hub_rerank`); `images` = assets; `rank=ppr` = model-free topic search |
180
+ | `read(concept_id, include?, max_tokens?)` | Full shapes, or a budgeted section list |
181
+ | `traverse(start_id, relationship?, direction?, target?, …)` | Walk edges, list dirs, connect two concepts |
182
+ | `ingest(kind, …)` | `md` / `pdf` / `thoughts` with per-kind params |
183
+ | `export_bundle(output_dir, …, flavor?)` | `okf` or `obsidian` |
184
+
185
+ Verify wiring with any `search` — an empty graph returns `[]`, which still
186
+ proves the plumbing works.
187
+
188
+ ### Skills
189
+
190
+ Harness-neutral skill sources live in `skills/` (Claude Code auto-loads
191
+ `.claude/skills/<name>/SKILL.md`; other harnesses have their own dirs — see
192
+ `docs/harness-integration.md`):
193
+
194
+ - **`okfgraph-mcp`** — finding knowledge via MCP tools (pick this *or* CLI).
195
+ - **`okfgraph-cli`** — same graph through `okf` shell commands.
196
+ - **`okfgraph-ingest`** — feeding discipline: what to store, which kind,
197
+ converter modes, wikilink/alias conventions. Install alongside either
198
+ finder when the agent should persist knowledge.
199
+
200
+ ---
201
+
202
+ ## Architecture
203
+
204
+ ```
205
+ ┌──────────── MCP (5 tools) / CLI (5 verbs + maintenance)
206
+ │ │
207
+ OKFRouter (facade: owns conn, encoder, lock; thin proxies)
208
+
209
+ ┌────────────┼──────────────────────────────────────────┐
210
+ │ │ │
211
+ Import ──► Embed ──► Search ◄── ranking ──► Export ──► Doctor/Diff
212
+ │ │ │ (seeds+PPR) │ │
213
+ │ │ │ links.py │
214
+ Delta ──► Schema ──► ImageAssets ──► Purge ──► Ingest ──► Converters
215
+ │ (bobine seam)
216
+ └──────────────── LadybugDB (graph + vector + FTS, one file) ──┘
217
+
218
+ Rust okf-embed (Jina v5, ORT) — tokenize, last-token
219
+ pool, L2-norm, Matryoshka truncate; numerics pinned
220
+ by tests/test_parity.py
221
+ ```
222
+
223
+ Components live in `okfgraph/components/` (one concern each, dependencies
224
+ injected): `search`, `ranking` (pure seeds + exact PPR), `links` (pure
225
+ extraction + name index), `import_` (batch/single upsert, delta-aware),
226
+ `export` (okf/obsidian flavors), `diff` (structural compare), `doctor`
227
+ (scored scan + safe fixes), `embedding` (Rust bridge + chunking),
228
+ `converters` (`DocumentConverter` protocol + `BobineConverter`),
229
+ `image_assets`, `delta`, `purge`, `schema`, `ingest`.
230
+
231
+ Key design decisions:
232
+
233
+ - **Rust-only embeddings, fail-fast** — no Python fallback; a mid-run stack
234
+ switch would silently mix vector spaces in one index.
235
+ - **Last-token pooling** (not mean) — required by Jina v5; mean pooling
236
+ breaks alignment with omni image embeddings.
237
+ - **Single pinned ORT** (`onnxruntime==1.29.0`, `ORT_DYLIB_PATH`-overridable)
238
+ shared by bobine + okf-embed.
239
+ - **Bobine is a plugin, not a dependency** — `DocumentConverter.convert()`
240
+ is the seam; provider owns its options (`routing_mode`,
241
+ `extract_images`); missing bobine → clear `RuntimeError`, never a silent
242
+ fallback path.
243
+ - **Deterministic by construction** — sorted traversal order, fixed PPR
244
+ constants and accumulation order, golden fixtures under `tests/fixtures/`
245
+ (PPR scores, diff reports, doctor scores, vault round-trips).
246
+ - **Bundle is source of truth, graph is the index** — edit markdown,
247
+ re-import; verify ingests by searching, since empty `[]` proves wiring
248
+ while errors prove broken setup.
249
+ - **MCP-first surface** — CLI mirrors the same 5 verbs; maintenance
250
+ (`diff`, `doctor`) is CLI-only to keep the agent tool surface at 5.
251
+
252
+ ---
253
+
254
+ ## API reference (`OKFRouter` proxies)
255
+
256
+ | Method | Description |
257
+ |---|---|
258
+ | `import_from_okf(file_path, mode?)` / `import_mgr.import_bundle(dir?, batch_size?, mode?, purge_deleted?)` | Single / bulk import |
259
+ | `ingest_mgr.ingest_md / ingest_pdf / ingest_thoughts` | Kind-based ingestion with lint + chunk + embed + link |
260
+ | `search_hybrid(query, …, rank="none"\|"hub"\|"ppr")` | RRF hybrid; `hub` blends authority, `ppr` is model-free |
261
+ | `search_engine.search_with_ppr / read_with_budget / search_chunks / traverse / find_path / get_chunks / get_by_id / list_directory` | Model-free PPR, budgeted reads, retrieval + navigation |
262
+ | `embed_engine.reconstruct_document(id)` / `count_tokens(text)` | ~98% chunk→markdown rebuild; Rust counter with chars/4 fallback |
263
+ | `export_mgr.export_bundle / export_to_okf(..., flavor="okf"\|"obsidian")` | Filtered export, both flavors |
264
+ | `diff_mgr.diff_dirs(old, new)` / `diff_db_dir(bundle_dir)` | Snapshot / drift structural reports |
265
+ | `diagnose(stale_days?)` / `doctor_fix()` | Health report / safe repairs |
266
+ | `list_broken_links()` / `repair_links(skip_sources?)` | Unresolved refs; exact-id + unique-name repair |
267
+ | `model_info(...)` / `default_cache_dir()` | Cache inspection without model load |
268
+
269
+ ---
270
+
271
+ ## Testing
272
+
273
+ ```bash
274
+ uv run --project . pytest tests/ -q # full suite (model loads; takes a while)
275
+ uv run --project . pytest tests/test_ranking.py tests/test_mcp_server.py -q # fast subset
276
+ ```
277
+
278
+ Golden fixtures under `tests/fixtures/` (`ppr_graph`, `diff_a`/`diff_b`,
279
+ `doctor_bundle`, `obsidian_vault`, `ppr_bundle`) lock deterministic behavior:
280
+ PPR scores byte-stable across runs, diff reports exact, doctor score pinned
281
+ (83 on the fixture bundle), obsidian export→import edge-identical. Live suites
282
+ reuse one class-scoped router each so the ONNX model loads once per class.
283
+
284
+ ---
285
+
286
+ ## Project structure
287
+
288
+ ```text
289
+ okfgraph/
290
+ ├── okfgraph/
291
+ │ ├── __init__.py # ConceptModel, OKFRouter, cli_main
292
+ │ ├── models.py # ConceptModel / ChunkModel / ImageAssetModel (extra frontmatter allowed)
293
+ │ ├── router.py # OKFRouter facade (owns resources, thin proxies)
294
+ │ ├── cli.py # okf: 5 verbs + maintenance, slim help, okfgraph.toml
295
+ │ ├── mcp_server.py # okf-mcp: 5 tools, MCP ≥ 2.0, lifespan-managed router
296
+ │ ├── config.py # okfgraph.toml + env + CLI merge
297
+ │ ├── images.py # IngestMode (text|optional|omni), planning helpers
298
+ │ ├── security.py # SSRF/domain guards for remote images
299
+ │ ├── tools.py # legacy tool definitions (superseded by mcp_server)
300
+ │ └── components/ # ranking, links, search, import_, export, diff, doctor,
301
+ │ # embedding, converters, image_assets, delta, purge, schema, ingest
302
+ ├── rust/okf-embed/ # Jina v5 Rust loader (ort, CUDA-opportunistic) + Python wheel
303
+ ├── skills/ # okfgraph-mcp, okfgraph-cli, okfgraph-ingest (harness-neutral source)
304
+ ├── tests/fixtures/ # conformance corpus: ppr, diff, doctor, obsidian, bundles
305
+ ├── docs/ # converters, harness-integration, plan-retrieval-roundup, diagnostics…
306
+ ├── .mcp.json # ready MCP wiring (uv run --project . okf-mcp)
307
+ ├── architecture.md # long-form architecture spec
308
+ └── pyproject.toml # slim core deps + pdf/omni/dev extras
309
+ ```
310
+
311
+ ---
312
+
313
+ ## Requirements
314
+
315
+ Core (`uv sync`): `ladybug==0.20.3`, `okf-embed` (built from `rust/okf-embed`),
316
+ `onnxruntime==1.29.0`, `mordant>=0.9`, `mcp>=2.0`, `pydantic>=2`, `pyyaml>=6`,
317
+ `numpy>=1.26`, `python-frontmatter>=1`, `fasteners>=0.19`. Python ≥ 3.11.
318
+
319
+ - `--extra pdf`: `bobine>=0.5` (default PDF converter).
320
+ - `--extra omni`: `sentence-transformers>=3`, `Pillow>=10` (image embeddings).
321
+ - `--extra dev`: `pytest>=8`.
322
+
323
+ ---
324
+
325
+ ## License
326
+
327
+ See LICENSE for details (Apache-2.0 OR MIT).
@@ -0,0 +1,7 @@
1
+ """OKF Knowledge Graph — Ladybug-backed knowledge system with ONNX + Jina v5 embeddings."""
2
+
3
+ from okfgraph.models import ConceptModel
4
+ from okfgraph.router import OKFRouter
5
+ from okfgraph.cli import main as cli_main
6
+
7
+ __all__ = ["ConceptModel", "OKFRouter", "cli_main"]