{
  "markdown": "# foodini-okf-ingest\n\n[![CI](https://github.com/Foodini-NG/foodini-okf-ingest/actions/workflows/ci.yml/badge.svg)](https://github.com/Foodini-NG/foodini-okf-ingest/actions/workflows/ci.yml)\n[![conformance](https://img.shields.io/badge/OKF%20conformance-passing-brightgreen)](#conformance-tests)\n[![deterministic](https://img.shields.io/badge/deterministic-no%20LLM%20agents-blue)](#deterministic-by-design--no-agents)\n[![python](https://img.shields.io/badge/python-3.14%2B-blue)](#install)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)\n\n> ### This is a fork\n>\n> **This file was modified by Foodini on 2026-08-22.** This repository is a\n> Foodini fork of [**okf-ingest**](https://github.com/travisjakel/okf-ingest) by\n> **Travis Jakel**, taken at commit `f3b58994` (release 0.11.0) and licensed\n> under Apache-2.0. See [`NOTICE`](NOTICE) for attribution and the full list of\n> changes.\n>\n> **What differs from the original.** The original ships five bindings — R,\n> Python, Rust, C++ and MATLAB — held byte-identical by a shared conformance\n> corpus. This fork keeps **Python only**, because Foodini does not write the\n> other four and holding them in lockstep would cost several implementations\n> per change. The catalog schema and the conformance corpus are retained\n> unchanged. The distribution is `foodini-okf-ingest` and the command is\n> `okf-ingest` (the original's bare `okf` collides with okf-generator).\n>\n> **This fork is independent.** Travis Jakel has not endorsed it and is not\n> responsible for it, so report problems *with this fork* here rather than to him.\n>\n> **If you want the tool rather than our build of it, go\n> [upstream](https://github.com/travisjakel/okf-ingest).** Five bindings instead\n> of one, actively developed, properly packaged — for most people it is simply the\n> better choice, and a fix contributed there helps far more users than the same\n> fix here. We forked to avoid maintaining four bindings we do not write, not\n> because we thought we could do it better.\n\nA unified, open-source **ingestion tool for [Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog) (OKF) bundles** — read any OKF bundle, validate its conformance (permissively, per the spec), build the concept graph, and load it into a portable, queryable **DuckDB catalog**. One catalog format, one idiomatic **Python** package.\n\n> **Point it at your `[[wikilink]]` vault.** As of 0.6, okf-ingest resolves both\n> markdown `](path)` links *and* `[[wikilink]]` references (Obsidian / Logseq /\n> Foam) — by **name** (id / alias / title), so links **survive file renames**.\n> Your existing notes become a deterministic, queryable, renderable knowledge\n> graph with no rewriting. See [`[[wikilinks]]` & aliases](#wikilinks--aliases).\n\n![okf-ingest graph of okf-ingest's own documentation bundle](docs/graph.png)\n\n> The image is `okf-ingest graph` run on [okf-ingest's own docs](docs/okf-bundle/) — the\n> project dogfoods OKF: that folder is a conformant bundle you can `ingest`,\n> `html`/`graph`, and `doctor` with the tool itself.\n\nOKF (Google Cloud; spec v0.2, v0.1 bundles fully supported) is a directory of markdown files with YAML frontmatter — one concept per file, markdown links as a graph. Validators and parsers already exist (Node, a web tool, a pure-Rust crate). **What no other tool does — and what this one is for — is load a bundle into a SQL-queryable DuckDB catalog with built-in semantic search (RAG) from Python**. See [Related tools](#related-tools).\n\n## Deterministic by design — no agents\n\nokf-ingest is **pure, deterministic machinery**: the same bundle in always\nproduces the same catalog, the same graph, the same clusters, and the same\nrendered HTML out — byte-for-byte, on any machine, with no network and no API\nkey. **There are no LLM agents anywhere in it.** It never asks a model to\nsummarize a page, infer a \"layer,\" guess a relationship, or decide anything. It\nreads exactly the structure the author wrote — the frontmatter, the markdown\nlinks — and surfaces *that*.\n\nThis is a deliberate line. A wave of tools will read your knowledge base by\nturning agents loose to summarize and \"understand\" it; their output is\nnon-reproducible, costs tokens, ships your corpus to a model, and quietly\ninvents structure. okf-ingest does the opposite — it's the boring, auditable\nsubstrate underneath:\n\n- **Reproducible** — deterministic enough to assert on in CI; a parity test locks\n  R and Python to byte-identical catalogs. No \"re-ran it and got different\n  edges.\"\n- **Free & offline** — no tokens, no keys, no calls. Parsing, validation, the\n  link graph, community clustering (deterministic label propagation), backlinks,\n  impact, and HTML/graph rendering are all plain code.\n- **Private** — your content never leaves the machine. Nothing is sent anywhere.\n- **Composable with agents, not replaced by them** — when you *do* want an LLM,\n  okf hands it the curated graph to reason over (`okf-ingest context`) rather than\n  pretending to be the reasoner. You bring the model; okf brings the ground truth.\n\n**The two honest exceptions**, both opt-in and explicit: the `embed`/`rag` layer\ncalls a *local, pluggable* embedding model (default Ollama — swap in your own) to\nadd vector search; and `ingested_at` is a wall-clock metadata field you can\noverride (the conformance suite does, which is how it stays byte-stable). The\nknowledge representation itself — concepts, graph, clusters, render — is 100%\ndeterministic and model-free.\n\n| | Wiki / docs site | Vector DB | Agent \"understand my wiki\" | **okf-ingest** |\n|---|---|---|---|---|\n| Reproducible (same in → same out) | ✅ | ⚠️ re-embed drift | ❌ non-deterministic | ✅ byte-locked |\n| Offline, no API key / tokens | ✅ | ⚠️ | ❌ | ✅ |\n| Content stays local / private | ✅ | ⚠️ | ❌ sent to a model | ✅ |\n| SQL / programmatic access | ❌ | ⚠️ vectors only | ⚠️ | ✅ DuckDB |\n| Explicit concept graph | ⚠️ implicit | ❌ | ✅ inferred | ✅ author-written |\n| Your `[[wikilink]]` notes → queryable graph | ⚠️ renders only | ❌ ignores links | ⚠️ non-deterministic | ✅ + rename-safe |\n| Renders to HTML + interactive graph | ⚠️ | ❌ | ✅ | ✅ |\n| Semantic search | ❌ | ✅ | ✅ | ✅ (opt-in) |\n| Invents structure with an LLM | ❌ | ❌ | ✅ | ❌ by design |\n\n### How it fits together\n\n```mermaid\ngraph LR\n  B[\"OKF bundle<br/>(dir · git · tar/zip)\"] --> I[ingest]\n  I --> C[(DuckDB catalog<br/>concepts · links · validation · chunks)]\n  C --> CX[\"context<br/>(LLM-wiki blob)\"]\n  C --> H[\"html<br/>(site / single)\"]\n  C --> G[\"graph / export<br/>(interactive · JSON · Mermaid)\"]\n  C --> D[\"doctor<br/>(health · --fix)\"]\n  C --> DF[\"diff<br/>(drift · snapshot changelog)\"]\n  C --> RK[\"rank<br/>(Personalized PageRank)\"]\n  C --> R[\"embed / rag<br/>(opt-in, local model)\"]\n```\n\n## Do you actually need RAG?\n\nOften you don't — by design. OKF bundles are meant to be read *directly* by an\nagent: load `index.md`, follow the curated links, pull the few relevant concept\nfiles into context. For a small, well-linked bundle (dozens to low-hundreds of\nconcepts), that index-first traversal is the intended pattern — cf. Karpathy's\n\"LLM wiki\"; Google's own framing is that OKF **complements** RAG, it doesn't\nrequire it. No catalog, no embeddings, no okf-ingest — just let the agent\nnavigate the markdown.\n\nReach for okf-ingest when direct reading isn't enough:\n\n- **Programmatic / SQL access** (any size) — query concepts, the link graph, or\n  conformance findings from code or CI, in R or Python. → the DuckDB catalog.\n- **Large bundles** — thousands of concepts, where loading the index or the whole\n  bundle into context isn't practical.\n- **Semantic / cross-corpus retrieval** — feeding OKF into a wider RAG pipeline,\n  or similarity search over a big/heterogeneous knowledge base. → the optional\n  `embed`/`rag` layer.\n\nFor small curated bundles, **skip `embed`/`rag`** — the explicit graph the author\nwrote beats fuzzy vector matches, and following links costs nothing. If you want\ntooling *for* that wiki pattern (rather than against it), use\n[`okf-ingest context`](#context--the-index-first-no-embeddings-primitive): it assembles\nthe index-first, link-following slice for an agent to read directly — no\nembeddings involved.\n\n## Quickstart\n\nInstall from a clone (see [Install](#install)), then:\n\n```bash\nuv pip install -e \".[html]\"    # from a clone; see Install\n\nokf-ingest ingest ./my-bundle --db kb.duckdb      # dir, git URL, or tar/zip\nokf-ingest embed  kb.duckdb\nokf-ingest rag    kb.duckdb --query \"how is revenue computed?\" -k 5\n# [0.71] metrics/revenue.md#1 — Revenue\n# [0.64] orders.md#1 — Orders\n```\n\nThe catalog is plain DuckDB — query it from Python, or with the bare `duckdb`\nCLI, or anything else that speaks DuckDB.\n\n## Use it from an AI agent\n\nokf is deterministic and **composes with your agent** — give the agent the graph,\nlet it reason. Drop this into your agent's instructions\n(`AGENTS.md` / `CLAUDE.md` / Cursor rules) so it drives okf instead of grepping:\n\n```\nThe docs at <PATH> are an OKF bundle. Use `okf-ingest` to navigate them:\n- `okf-ingest context <PATH> --start <concept>.md --depth 1` → index.md + that concept +\n  its linked neighbours as one markdown blob. Read that; don't grep files ad hoc.\n- `okf-ingest query <PATH> --search \"<term>\"` (substring) or `--sql \"<SELECT…>\"` for lookups.\nStart from index.md to see the map.\n```\n\nThat's it — one paste and the agent reads the bundle index-first, the way OKF is\nmeant to be consumed (`okf-ingest` runs locally; no data leaves the machine). For\nsemantic instead of substring lookup, ingest once (`okf-ingest ingest <PATH> --db kb.duckdb`),\n`okf-ingest embed kb.duckdb`, then point the agent at `okf-ingest rag kb.duckdb --query \"…\"`. See\n[`context`](#context--the-index-first-no-embeddings-primitive).\n\n## The core is a contract, not compiled code\n\nTwo language-neutral artifacts define behaviour, and this fork keeps both unchanged:\n\n1. **`schema/catalog.sql`** — the DuckDB catalog schema. You can query the catalog with the bare `duckdb` CLI, no library at all, and a catalog written here is readable by any conformant implementation of OKF ingestion — including the original's other bindings.\n2. **`conformance/`** — language-agnostic golden bundles plus expected outputs that the implementation must reproduce, including a `content_hash` parity lock and a hidden-directory guard.\n\nIn the original these existed to hold five independent implementations byte-identical. Here there is one implementation, so they serve as a **behavioural regression gate** instead — and an unusually strict one, precisely because every asserted value had to be reproducible in five languages. Any change that moves a conformance-asserted value must update the expected JSON deliberately and say why.\n\n## What it enforces (and tolerates)\n\nPer OKF §6, a bundle is **conformant** iff every non-reserved `.md` has parseable YAML frontmatter with a **non-empty `type`** (a free string — no enum). Everything else is permissive: missing recommended fields, unknown types/keys, broken links, and missing `index.md` produce **findings, never rejection**. See [`docs/SPEC_NOTES.md`](docs/SPEC_NOTES.md).\n\n## Install\n\nPython **3.14+**. The project is uv-managed and is not published to any index —\ninstall from a clone.\n\n```bash\ngit clone git@github.com:Foodini-NG/foodini-okf-ingest.git\ncd foodini-okf-ingest\nuv venv --python 3.14\nuv pip install -e \".[html]\"\n```\n\nThat puts the **`okf-ingest`** command on PATH inside `.venv`, plus the\nimportable `okf` package. The `[html]` extra adds the markdown engine for\n`okf-ingest html`; embeddings and `rag` use a local Ollama server by default and\nneed no extra Python dependency (the embedder is pluggable — see\n[Semantic search](#semantic-search-rag)).\n\nWithout installing: `PYTHONPATH=src python -m okf …`.\n\n> The command is **`okf-ingest`**, not `okf`. Both OKF tools claimed the bare\n> `okf`, so on a machine with okf-generator installed `okf --help` could run the\n> other tool. Renaming it is one of this fork's changes.\n\n## Usage\n\n```python\nimport okf.okf as okf\ncon, summary = okf.ingest(\"path/to/bundle\", db_path=\"catalog.duckdb\")\nokf.search(con, \"revenue\")\n```\n\nBoth produce the same `okf_bundle / okf_concept / okf_link / okf_validation` tables.\n\n### Semantic search (RAG)\n\n> Optional, and overkill for small curated bundles — see\n> [Do you actually need RAG?](#do-you-actually-need-rag). It pays off for large\n> or cross-corpus knowledge bases, not a hand-linked folder of a few dozen concepts.\n\n`embed` chunks concept bodies (paragraph-merged to ~600 chars), embeds each via\na **pluggable embedder** (default: local Ollama `nomic-embed-text`, 768-dim;\nswap in any `texts -> list[vector]` callable), and stores vectors in `okf_chunk`.\n`rag` embeds a query and ranks chunks by cosine similarity using DuckDB's native\n`list_cosine_similarity` — **no vector-DB extension required**. Embeddings are\npart of the shared catalog, so you can embed with one binding and query with the\nother.\n\n## Without the catalog — the lean parse / lint / graph layer\n\nThe DuckDB catalog is the *queryable materialization*; it isn't on the critical\npath for parsing, linting, or graphing a bundle. If you don't want to touch\nDuckDB at all, three functions hand you the whole model as plain data structures\n(R: data frames / lists; Python: dicts / dataclasses) and **never go through the\ncatalog**:\n\n| | R | Python | Rust | Gives you |\n|---|---|---|---|---|\n| Parse | `okf_read()` | `read_bundle()` | `read_bundle()` | concepts (frontmatter + body) |\n| Lint / health | `okf_validate()` | `validate()` | `validate()` | the *same* findings `doctor` reports — broken links, orphans, missing fields, non-ISO timestamps |\n| Graph | `okf_links()` | `links()` | `links()` | resolved + broken edges (markdown **and** `[[wikilinks]]`) |\n| Diff | `okf_diff(a, b)` | `diff(a, b)` | `diff(a, b)` | dir-vs-dir changelog (concepts, types, edges) — the catalog only enters if a side *is* one |\n\n```r\nrd  <- okf_read(\"my-bundle\")        # no DuckDB\nval <- okf_validate(rd)             # data.frame of findings (broken_link, orphan, …)\nlk  <- okf_links(rd)                # src_path / dst_raw / dst_path / resolved\nsubset(lk, !resolved)               # every dangling link, as data — your call what to do\n```\n\n```python\nb   = okf.read_bundle(\"my-bundle\")  # no DuckDB\nval = okf.validate(b)               # list of findings\nlk  = okf.links(b)                  # the edge graph\n[e for e in lk if not e[\"resolved\"]]\n```\n\n```rust\n// The Rust crate IS this layer (plus rank/seeds/diff/fetch), catalog-free:\nlet ing = okf_ingest::ingest(\"my-bundle\")?;   // summary + findings + links in memory\nlet broken: Vec<_> = ing.links.iter().filter(|l| !l.resolved).collect();\n```\n\nReach for the catalog (`okf_ingest`) when you want what a SQL engine is *for*:\nSQL `query`, the rendered `html`/`graph`, `context` blobs, or vector `rag`. So\nDuckDB is effectively opt-in by usage — build it when you need it, ignore it when\nyou don't.\n\n## CLI\n\nAfter install, `okf-ingest …` (the console script). Without installing,\n`PYTHONPATH=src python -m okf …` takes the same arguments.\n\n```bash\nokf-ingest validate <bundle> [--subdir <p>] [--strict] [--json]   # lint; exit 1 on errors (or warnings w/ --strict)\n                     [--summary] [--severity S,..] [--rule R,..] [--exclude-rule R,..]\n                     [--path G,..] [--exclude-path G,..]           # narrow the findings; globs or path prefixes\nokf-ingest ingest   <source> --db catalog.duckdb [--subdir <p>] [--branch <b>] [--incremental] [--json]\nokf-ingest query    catalog.duckdb [--sql \"…\"] [--search <term>] [--concepts|--links|--findings] [--json]\n                    [--bundle <id>]     # only needed if the catalog holds more than one bundle\nokf-ingest context  <bundle|catalog> [--start <concept>] [--depth N] [--max-tokens N]  # LLM-wiki context blob\nokf-ingest html     <bundle|catalog> --out <dir> | --single <file.html> [--title T]    # render for viewing\nokf-ingest graph    <bundle|catalog> --out <file.html> [--title T]                     # interactive force-directed graph\nokf-ingest export   <bundle|catalog> [--json]                      # portable {nodes, edges} graph JSON\nokf-ingest impact   <bundle|catalog> <concept> [--json]            # inbound / outbound / transitive ripple\nokf-ingest rank     <bundle|catalog> <concept> [-k N]              # Personalized PageRank relevance to a concept\nokf-ingest diff     <a> <b> [--json]                               # concept-level changelog; each side a bundle dir or catalog\nokf-ingest embed    catalog.duckdb [--model nomic-embed-text] [--incremental]  # chunk + embed bodies for search\nokf-ingest rag      catalog.duckdb --query \"…\" [-k 5] [--model …]  # top-k semantic matches\n```\n\n### `context` — the index-first, no-embeddings primitive\n\n`context` is the faithful OKF / \"LLM wiki\" consume operation: hand an agent\n`index.md` plus a concept and its **link-neighborhood**, assembled into one\nmarkdown blob to read directly. It walks the concept graph you already built —\n**no embeddings, no vector search** — and is capped to a token budget. This is\nthe on-concept alternative to `rag` for curated bundles:\n\n```bash\nokf-ingest context ./my-bundle --start orders.md --depth 1 --max-tokens 8000 > ctx.md\n# emits index.md + orders.md + everything one link away, ready to paste into a prompt\n```\n\nIt accepts a bundle directly (dir/git/tar/zip) or an ingested `.duckdb` catalog.\n\n### `html` — render a bundle for viewing\n\n`html` is a thin \"render for viewing\" layer: turn a bundle into browsable HTML\nwith **no build step, no JavaScript, inline CSS** — copy the output anywhere and\nopen it. Two modes:\n\n```bash\nokf-ingest html ./my-bundle --out site/            # navigable site: one .html per concept + index.html\nokf-ingest html ./my-bundle --single bundle.html   # one self-contained file (concepts become anchored sections)\n```\n\nInternal `.md` links are rewritten to **page-relative** `.html` (site) or\nin-page `#anchors` (single), so the result works straight off the filesystem\n(`file://`) however the source wrote its links. Each page gets a metadata bar\n(type / status / timestamp / tags), a **\"Linked from\"** backlinks line, and a\nfooter badge that surfaces broken or orphan links from `validate`. Bodies render\nvia a thin markdown engine (R `commonmark`, a Suggests dep; Python `markdown` via\nthe `okf-ingest[html]` extra). Like `context`, it accepts a bundle\n(dir/git/tar/zip) or a `.duckdb` catalog.\n\n### `graph` / `export` / `impact` — the concept graph, surfaced\n\nThe catalog already holds the link graph; these expose it (all **deterministic**,\nno LLM):\n\n```bash\nokf-ingest graph  ./my-bundle --out graph.html   # interactive force-directed page (vanilla JS, no CDN)\nokf-ingest export ./my-bundle > graph.json       # portable {nodes, edges} for any external visualizer\nokf-ingest impact ./my-bundle signals/x.md       # outbound / inbound / transitive ripple of a concept\n```\n\n`graph` is a single self-contained HTML page — pan/zoom/drag, type-to-search,\nnodes coloured by OKF type with community clustering as the fallback (a\ndeterministic label-propagation, [`okf_clusters`]). Click a node to open its\nrendered `.html`, so dropping `graph.html` into a `html --out` site root turns it\ninto a live map. `export` emits the same node/edge model as JSON (nodes carry\n`id`/`type`/`title`/`tags`/`cluster`/`href`), extending the \"core is a contract\"\nidea beyond the DuckDB catalog. `impact` answers \"what does changing this ripple\nto\" from the resolved-link graph.\n\n### `--incremental` — re-ingest / re-embed only what changed\n\n`ingest --incremental` diffs each concept's `content_hash` against a prior ingest\ninto the same `--db`, rewriting only changed/added concepts (and dropping removed\nones); the JSON summary reports `changed`/`added`/`removed`/`cached`. `embed\n--incremental` re-embeds only concepts whose content changed, skipping the\nexpensive embedder calls for the rest — the right default for large, often-edited\nwikis.\n\n### `doctor` — ongoing health & maintenance\n\nKnowledge bases drift — links break when files move, timestamps go stale,\nconcepts orphan. `okf-ingest doctor` is a deterministic one-shot health scan with a\nscore and CI exit codes:\n\n```bash\nokf-ingest doctor ./my-bundle                  # health: 92/100 (broken links, orphans, stale ts, dup titles…)\nokf-ingest doctor ./my-bundle --strict         # exit 1 on any warning — drop into CI / a hook\nokf-ingest doctor ./my-bundle --stale-days 365 # also flag timestamps older than a year\nokf-ingest doctor ./my-bundle --fix            # apply ONLY safe repairs, report each\n```\n\n`doctor` also flags `duplicate_identity` (one id/alias claimed by two\nconcepts — breaks `[[wikilink]]` resolution) and, at info level (never hurts\nthe score), `hub_concentration` (pages whose links mostly point at hubs).\nPages marked **`reviewed: true`** in frontmatter are human-validated:\n`doctor --fix` never touches them.\n\n`--fix` is conservative on purpose — it only normalizes a parseable non-ISO\n`timestamp`, and re-points a broken link when *exactly one* basename matches.\nAnything ambiguous is reported, never guessed (no LLM). Ready-made\n[`examples/pre-commit`](examples/pre-commit) and\n[`examples/github-action.yml`](examples/github-action.yml) wire it into your\nworkflow so a bundle can't drift broken.\n\n### `rank` — relevance, from the graph the author wrote\n\n`okf-ingest rank` scores every concept's relevance to a start concept with\n**Personalized PageRank** — computed by *exact power iteration*, not\nMonte-Carlo sampling, so it is fully deterministic like everything else here.\nNo embeddings, no model: the signal is the link structure the bundle's author\nalready encoded.\n\n```bash\nokf-ingest rank ./my-bundle orders.md            # what matters most to orders.md, ranked\nokf-ingest context ./my-bundle --start orders.md --rank ppr   # budget-fill context by relevance\n```\n\n`context --rank ppr` upgrades neighborhood selection from BFS (\"everything at\ndepth 1 is equal\") to relevance-weighted — on hub-heavy wikis the pages that\nactually matter to the topic fill the token budget first instead of whatever\nthe hub happens to link. A conformance fixture locks R and Python to\nbyte-identical scores. (Programmatic: `okf_rank()` / `okf.graph.ppr()`.)\n\nMeasured, not asserted — [`bench/`](bench/) runs a leave-one-link-out\nretrieval benchmark on real corpora: on a hub-heavy living wiki,\nquery-seeded exact PPR nearly doubles BFS recall (R@5 0.29 → 0.54) and\nmatches local vector embeddings with zero embedding infrastructure, while a\nMonte-Carlo PPR baseline changes 7–24% of its top-5 between identical runs —\nexact power iteration is bit-stable at milliseconds per query. Full tables\nand honest caveats in [`bench/README.md`](bench/README.md).\n\n**Don't know which concept to start from? Ask a question.** `context --query`\nis the full hybrid cascade, still with zero models: deterministic lexical\nseed selection (`okf_seeds()`: +3 title / +2 description·tags / +1 body per\nquery token) → multi-seed PPR weighted by those scores → relevance-filled\ncontext:\n\n```bash\nokf-ingest context ./my-bundle --query \"how is revenue computed?\" --max-tokens 4000\n```\n\n### `diff` — what changed, as knowledge structure\n\n`git diff` shows text hunks; `okf-ingest diff` shows what changed as **knowledge\nstructure**: concepts added / removed / changed (by `content_hash`),\nfrontmatter `type`/`title` changes, and graph deltas — edges added/removed,\nlinks newly broken or fixed. Each side can be a bundle directory *or* an\ningested `.duckdb` catalog, which gives you both shapes for free:\n\n```bash\nokf-ingest diff catalog.duckdb ./my-bundle     # DRIFT: what changed since the last ingest\nokf-ingest diff ./snapshot-old ./snapshot-new  # SNAPSHOT: changelog between two versions\n```\n\n```\nconcepts: +1 added / -1 removed / ~2 changed (1 unchanged), type-changed 1, retitled 1\n  + delta.md\n  - gamma.md\n  ~ beta.md\n  ~ alpha.md  type: Signal -> Dataset\nlinks: +2 added / -1 removed, newly broken 1, fixed 1\n  ! beta.md -> gone.md (now broken)\n```\n\nLike everything in the core it's deterministic — pure hash/set comparison,\noutput sorted by path, no model, no wall clock — and exits `0` when identical,\n`1` when different, so `okf-ingest diff` drops straight into CI as a change gate the\nsame way `doctor` gates health. (Programmatic: `okf_diff(a, b)` in R,\n`okf.diff(a, b)` in Python; both also accept an open connection or an\n`okf_read()` bundle.)\n\n### `[[wikilinks]]` & aliases\n\nAlongside markdown `](path.md)` links (resolved by path, unchanged), okf-ingest\nresolves `[[wikilink]]` references — `[[Concept Name]]` and `[[name|display]]` —\nby **name**, trying `id` → `alias` → `title` → filename-stem (ambiguous names\nresolve to nothing, never a guess). Add `aliases: [Alt Name]` and an optional\n`id:` to a concept's frontmatter to give it stable handles. This makes\nokf-ingest work on Obsidian / Logseq / Foam-style vaults out of the box, and —\nbecause links target a name, not a path — they survive file renames. Existing\nmarkdown-only bundles are byte-identical; wikilinks are purely additive.\n\nA `<source>` is a local directory, a **git URL** (github/gitlab/bitbucket, `.git`,\nor `git@`), or a **tar/zip archive** (local path or `http(s)` URL). Remote sources\nare fetched to a temp dir and cleaned up automatically; `--subdir` selects a\nbundle within a repo/archive and `--branch` picks a git ref:\n\n```bash\nokf-ingest ingest https://github.com/org/repo.git --subdir docs/okf --db kb.duckdb\nokf-ingest ingest https://example.com/bundle.tar.gz --db kb.duckdb\n```\n\n`validate` is CI-friendly (non-zero exit = non-conformant). The catalog is a\nplain DuckDB file and the schema is the interop contract, so anything that\nspeaks DuckDB can read it — including the bare CLI:\n\n```bash\nokf-ingest ingest ./bundle --db cat.duckdb\nduckdb cat.duckdb -c \"SELECT path, title FROM okf_concept ORDER BY path\"\n```\n\n## Conformance tests\n\n```bash\npython conformance/check_py.py      # vs conformance/expected/*.json — must print PASS\n```\n\nStdlib-only and standalone. It gates every change; CI runs it on every push and\npull request.\n\n## Layout\n\n```\npyproject.toml          the package: foodini-okf-ingest, console script okf-ingest\nsrc/okf/                the implementation (okf.py, cli.py, graph.py, html.py,\n                        doctor.py, diff.py, rag.py)\nschema/catalog.sql      the catalog schema (interop contract) — unchanged from upstream\nconformance/            golden bundles + expected outputs + check_py.py (the gate)\ndocs/                   ARCHITECTURE.md, SPEC_NOTES.md, okf-bundle/ (dogfood)\nbench/                  retrieval benchmark + published results\nexamples/               a GitHub Action and a pre-commit hook\nNOTICE                  attribution and the list of changes from upstream\n```\n\n## Status\n\n**Stable feature surface · maintained by Foodini for Foodini's use.** The whole\nconsume side is implemented, CLI-wrapped and conformance-tested over one\nportable DuckDB catalog: **validate → ingest → query → context → render\n(`html` / `graph` / `export --mermaid`) → `impact` → `rank` → `doctor` → `diff`\n→ embed → rag**, with `--incremental` ingest/embed and dir/git/tar/zip sources.\n\nThe feature surface came from upstream complete and the conformance contract is\nlocked, so behaviour is stable. What this fork does *not* offer is a general\nsupport commitment: it exists to serve Foodini's knowledge pipeline, it is not\npublished to any package index, and it will diverge from upstream over time.\nIssues and PRs are welcome here. But if your change is generally useful rather\nthan specific to how Foodini runs this, **please take it to\n[the original](https://github.com/travisjakel/okf-ingest) instead** — it has five\nbindings to our one, it is actively developed, and a fix landed there helps far\nmore people. See [CONTRIBUTING.md](CONTRIBUTING.md) for where the line falls.\n\n## Roadmap\n\n- **Authoring** (`new` / `add`) — scaffold conformant concepts. Deliberately not\n  done yet; okf-ingest is consume-first (for authoring today, see\n  [`okf-knowledge`](https://github.com/sniperunder123/okf-knowledge)).\n- **`watch`** — re-ingest/render on file change for live editing.\n- **HTML polish** — optional sidebar nav and theme palettes (the page stays\n  no-JS, inline-CSS).\n- More `doctor --fix` classes, as long as they stay unambiguously safe.\n\n## FAQ\n\n**Is anything sent to an LLM?** No — not by the core. The only model calls are\nthe opt-in `embed`/`rag` layer, and that's a *local* embedder (Ollama by\ndefault) you can swap. Parsing, the graph, clusters, rendering, and `doctor` are\nplain deterministic code. See [Deterministic by design](#deterministic-by-design--no-agents).\n\n**R or Python — which catalog do I get?** The same one. Both write byte-identical\nDuckDB catalogs (a parity test enforces it); ingest in one, query from the other.\n\n**Do I need embeddings/RAG?** Usually not for small curated bundles — the graph\nthe author wrote beats fuzzy matches, and `okf-ingest context` costs nothing. See\n[Do you actually need RAG?](#do-you-actually-need-rag).\n\n**How do I keep a bundle healthy over time?** `okf-ingest doctor` (+ the\n[`examples/`](examples/) pre-commit hook / GitHub Action) gates drift in CI;\n`--fix` repairs the unambiguously-safe issues.\n\n**Does it author/edit my knowledge?** No. It reads what you wrote. `doctor --fix`\nmakes only mechanical, reported repairs (ISO timestamps, unique-match moved\nlinks) — never content.\n\n**What's \"conformant\"?** Parseable frontmatter with a non-empty `type` on every\nnon-reserved file. Everything else is a finding, never a rejection.\n\n**Contributing?** See [CONTRIBUTING.md](CONTRIBUTING.md) — new bindings just need\nto reproduce the conformance corpus.\n\n## Related tools\n\nThe OKF tooling ecosystem appeared within weeks of the v0.1 spec. okf-ingest is\ndeliberately positioned where the others aren't — a queryable catalog + RAG, in\nR and Python:\n\n| Tool | Lang | Validate | Parse/graph | `[[wikilinks]]` | Queryable store | Embeddings / RAG |\n|------|------|:--:|:--:|:--:|:--:|:--:|\n| `GoogleCloudPlatform/knowledge-catalog` | Py/TS | — | producer + HTML viz | — | — | — |\n| `W4G1/okf` | Rust | ✓ | ✓ | — | — | — |\n| `sniperunder123/okf-knowledge` | Python (Claude Code skill) | ✓ | ✓ + **authoring & graph viz** | — | — | — |\n| WitsCode / okf.site | Node/web | ✓ | partial | — | — | — |\n| okf-skills / okf-skill | agent skills | ✓ | ✓ | — | — | — |\n| **okf-ingest** (this) | **R + Python** | ✓ | ✓ | **✓ rename-safe** | **DuckDB catalog** | **✓** |\n\nokf-ingest sits on the **consume** side of the OKF lifecycle. For the **produce**\nside — authoring, maintaining, and visualizing bundles (especially inside Claude\nCode) — [`okf-knowledge`](https://github.com/sniperunder123/okf-knowledge) is a\nnice complement: curate a bundle there, then `okf-ingest ingest` it into a queryable\nDuckDB + RAG catalog here. If you only need to lint a bundle, the Rust/Node\nvalidators are great.\n\n## License\n\nApache-2.0, unchanged from the original work. See [`LICENSE`](LICENSE) for the\nlicence text and [`NOTICE`](NOTICE) for attribution to Travis Jakel, the fork\npoint, and the list of modifications. Files modified by Foodini carry a notice\nsaying so, per Apache-2.0 section 4(b).\n",
  "bytes": 31285,
  "sha": "9d3788cd1fcee59e08cc8f375f041d27c61adc8c6738c8e40b82b87541bbbeae",
  "repo_slug": "foodini-ng/foodini-okf-ingest",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_foodini_ng_foodini_okf_ingest_conformanc_21591a45/readme"
}