# okf-gem — full documentation > Generated from https://okfgem.com/docs/ — the Open Knowledge Format toolkit for Ruby. --- # Getting started URL: https://okfgem.com/docs/getting-started/ Summary: Install okf-gem, validate your first directory, and open the live knowledge graph in under five minutes. Runs on every Ruby since 2.4 with three dependencies. ## Install The gem runs on every Ruby since 2.4, which is older than anything a current OS ships, with exactly three runtime dependencies (`rack`, `webrick`, and `minifts`): ```bash gem install okf # or, inside a project bundle add okf ``` Using Claude Code? The [plugin](/docs/plugin/) installs the whole toolchain, skill and curation hook included, in two commands. Everywhere else, the gem is the way in. No Ruby on the box? Run okf straight from the [Docker image](/docs/docker/), the same CLI with nothing to install. ## Point it at a directory Any folder of Markdown files with YAML frontmatter is close to a bundle already. Check where it stands: ```bash okf validate docs/ ``` A conformant bundle exits `0`. Errors mean one of three things is missing (parseable frontmatter, a non-empty `type`, or a reserved file that breaks its rules); the [conformance model](/docs/conformance/) walks each one. Warnings never block you. ## See the graph ```bash okf server docs/ ``` ```text serving 37 concepts at http://127.0.0.1:8808 (Ctrl-C to stop) ``` Concepts render as nodes, plain Markdown links between them as edges. Click a node, read its Markdown, follow its backlinks. This is the same UI as the [public demo](https://demo.okfgem.com), which serves the gem's own bundle. ## Ask about quality ```bash okf lint docs/ ``` `validate` asks "is this legal OKF?"; `lint` asks "is it well curated?": orphans, missing concepts people link to, stubs, uncited claims. It is advisory by default and exits `0`, so nothing breaks until you opt into gating with `--fail-on warn`. The [curation model](/docs/curation/) documents all sixteen checks. ## Teach your agent The gem ships the [OKF agent skill](/docs/skill/), so a coding agent can author and maintain the bundle for you: ```bash okf skill .claude # Claude Code -> .claude/skills/okf okf skill .agents # agent-agnostic -> .agents/skills/okf ``` ## Where next - [Your first bundle](/docs/guides/first-bundle/), a guided path from five files to a healthy graph. - [Bundle anatomy](/docs/bundle-anatomy/), what each file and field means. - [The CLI overview](/docs/cli/), every verb and the conventions they share. - [The OKF v0.1 spec](/docs/spec/), the format itself, exactly as shipped with the gem. --- # Run with Docker URL: https://okfgem.com/docs/docker/ Summary: Run every okf command without installing Ruby. The official image bundles the CLI and the graph server; mount your bundle at /data and go. Published multi-arch on ghcr.io. ## When to use it The gem is pure Ruby and runs on the Ruby your OS already ships, so `gem install okf` is the shortest path on most machines. Reach for the image when the Ruby is not yours to control: a CI job on a stack you would rather not add a toolchain to, a Kubernetes step, a reviewer's laptop, or any box where pulling an image beats installing a gem. The image carries the same CLI, so every verb behaves the same way. [Run okf with Docker](/blog/run-okf-with-docker/) is the full walkthrough. ## Pull and run The image lives at `ghcr.io/serradura/okf` and mounts your bundle at `/data`. Point any read verb at the mount: ```bash docker run --rm -v "$PWD:/data" ghcr.io/serradura/okf validate . docker run --rm -v "$PWD:/data" ghcr.io/serradura/okf lint . docker run --rm -v "$PWD:/data" ghcr.io/serradura/okf search . "graph server" ``` `--rm` cleans up the container after each run, and `-v "$PWD:/data"` maps the current directory onto `/data`, the image's working directory, so the bundle is just `.`. The whole CLI is the entrypoint, so whatever you would type after `okf` you type after the image name. Images are published for `linux/amd64` and `linux/arm64`. `:latest` tracks the newest release; pin a tag like `:1.9.0` when you want CI to stay put. ## Serve the graph The [graph server](/docs/cli/server/) needs two extra flags in a container. It binds to `127.0.0.1` by default, which nothing outside the container can reach, so bind it to `0.0.0.0` and publish the port: ```bash docker run --rm -v "$PWD:/data" -p 8808:8808 \ ghcr.io/serradura/okf server . --bind 0.0.0.0 ``` Then open `http://127.0.0.1:8808` on the host. The server reads bodies from disk on each request, so edits to the mounted bundle show on the next click with no restart. Ctrl-C, or `docker stop`, shuts it down cleanly. ## Mounts and exit codes A read-only mount is enough for every command above (`validate`, `lint`, `search`, `index`, the views, and `server` all only read), so you can harden the run with `-v "$PWD:/data:ro"`. The one verb that writes is `okf skill `, which needs a writable mount. Exit codes are unchanged from the CLI, so the image drops straight into a pipeline: `0` success, `1` a non-conformant bundle or a crossed `lint --fail-on` threshold, `2` a usage error. Gating a job on the image is the same contract as [gating on the gem](/docs/guides/ci/). ## Skip the prefix: install okf Typing the full `docker run` line every time gets old. The installer drops a tiny script named `okf` on your PATH, so the image takes the exact CLI interface: `okf validate .`, `okf lint .`, `okf server .`. Because the command is `okf`, the [agent skill](/docs/skill/) and everything else that calls `okf` keep working, with no Docker to think about. It mounts your directory at `/data`, and for `server` it publishes the port and adds `--bind 0.0.0.0`, so nothing extra to remember. Install it with one line (do this only on a machine without the gem, since it adds an `okf` command): ```bash curl -fsSL https://docker.okfgem.com/install.sh | sh ``` Or do it by hand, which is the same three steps the installer runs: ```bash curl -fsSL https://docker.okfgem.com/okf -o /usr/local/bin/okf chmod +x /usr/local/bin/okf okf --version ``` Then every verb reads like the CLI: ```bash okf validate . okf server . # port published and bound for you okf search . "graph server" ``` The command runs `ghcr.io/serradura/okf:latest` by default; set `OKF_IMAGE` to pin a version. Docker still does the work, so the same privacy and offline story holds. ## On Windows The image is Linux, and Docker Desktop runs it on Windows through WSL2, so `pull` and `run` behave the same. Two things differ from the examples above, and both are about the shell, not the image. The mount variable is spelled differently. In PowerShell use `${PWD}`; in Command Prompt use `%cd%`: ```powershell # PowerShell docker run --rm -v "${PWD}:/data" ghcr.io/serradura/okf validate . docker run --rm -v "${PWD}:/data" -p 8808:8808 ghcr.io/serradura/okf server . --bind 0.0.0.0 ``` ```bat REM Command Prompt docker run --rm -v "%cd%:/data" ghcr.io/serradura/okf validate . ``` The graph is still at `http://127.0.0.1:8808` on the host, because WSL2 forwards the port. The `okf` command has a PowerShell installer too, one line: ```powershell irm https://docker.okfgem.com/install.ps1 | iex ``` Then `okf validate .` and `okf server .` behave exactly as they do on macOS or Linux. If PowerShell says running scripts is disabled, allow local scripts once with `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`. The bash `install.sh` also works on Windows under WSL or Git Bash. ## Where next - [The CLI overview](/docs/cli/), every verb the image runs. - [okf server](/docs/cli/server/), the flags the graph server accepts. - [Gate drift in CI](/docs/guides/ci/), the exit-code contract in a pipeline. - [docker.okfgem.com](https://docker.okfgem.com), the image at a glance. --- # Why OKF URL: https://okfgem.com/docs/why-okf/ Summary: Project knowledge lives scattered across wikis, agent memory, and people's heads. The Open Knowledge Format gives it one durable, diffable home in git, checked by tooling. Project knowledge drifts. It scatters across wikis and chat, and the parts that are written down quietly stop matching the code. This documentation drift is invisible to an agent, which reads the stale file and inherits the wrong assumption. The Open Knowledge Format gives that knowledge one home a tool can check. The fuller case is in [Why okf-gem](/blog/why-okf-gem/). ## The problem Project knowledge (why a service exists, what a metric really measures, the reasoning a schema encodes) lives scattered across wikis, code comments, and whoever happened to be in the room. An agent re-derives it every session. A new hire re-asks it every onboarding. [OKF](https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing) is an open, vendor-neutral format published by Google Cloud in 2026. It gives that knowledge one durable home: a directory of Markdown files with YAML frontmatter, versioned next to the code it describes, read from the same file by people and agents alike. Each file is a concept; a directory of them is a [bundle](/docs/bundle-anatomy/). ## Compared to where knowledge lives now Knowledge already has several homes near an agent, and each holds a different thing. None of the others is built for curated, durable team knowledge:
| | OKF bundle | CLAUDE.md / AGENTS.md | Agent auto-memory | Wiki / Notion | |---|---|---|---|---| | Holds | curated team knowledge | standing instructions | what one agent picked up | human docs | | Versioned with the code | yes | yes | no | no | | Portable across agents | plain Markdown + YAML | per-harness conventions | per-agent store | export needed | | Typed and queryable | frontmatter + graph | prose | no | partially | | Reviewed in PRs | yes | yes | implicit | rarely | | Scales past one context window | progressive disclosure ([okf index](/docs/cli/index/)) | loaded whole | partially | n/a | | Checked by tooling | [validate](/docs/cli/validate/) + [lint](/docs/cli/lint/), exit codes for CI | no | no | no |
The last row is this gem's job. The other homes have no detector, so their drift stays invisible; a bundle's drift shows up as findings you can gate on in [CI](/docs/guides/ci/). ## Why an open standard matters A format only one tool understands dies with that tool. OKF is vendor-neutral by design: your bundles outlive okf-gem, and every tool anyone builds for the format makes every existing bundle more valuable. The spec itself [ships inside the gem](/docs/spec/), so the version your tooling enforces is the version you can read. ## Where the gem fits okf-gem is the Ruby-native toolkit for the format, three tools in one install: - the [agent skill](/docs/skill/) authors and curates bundles with judgment; - the [CLI](/docs/cli/) validates, lints, and reads them deterministically; - the [graph server](/docs/graph-server/) makes them explorable by humans. All of it runs 100% local. No account, no telemetry, no upload: your knowledge stays in your repo. --- # Bundle anatomy URL: https://okfgem.com/docs/bundle-anatomy/ Summary: What an OKF bundle is made of: concept files with YAML frontmatter, plain Markdown cross-links that become graph edges, index.md maps, and a dated log.md history. ## A bundle is a directory No database, no manifest, no build step. A bundle is a folder; each concept is one Markdown file whose path is its id. okf-gem's own repository documents itself in OKF, so this tree is real: ```text .okf/ ├── index.md # progressive-disclosure map (root carries okf_version) ├── log.md # ISO-dated change history, newest first ├── overview.md ├── format/frontmatter.md ├── model/graph.md └── capabilities/graph-server.md # one concept = one file ``` ## A concept is Markdown + frontmatter The only hard requirement is YAML frontmatter with a non-empty `type`. Everything else is recommended and tolerated when missing: ```md --- type: Capability title: Interactive graph server (server) description: A self-contained HTML knowledge graph served over HTTP. resource: lib/okf/server/app.rb tags: [server, graph, rack] timestamp: 2026-07-11T12:00:00Z --- # Overview `okf server` boots an interactive view of the [graph](../model/graph.md) ... ``` The fields carry distinct jobs: `type` classifies (Service, Metric, Decision, Playbook, whatever vocabulary fits your domain), `description` is the one-line summary every listing reuses, `resource` bridges the concept to the asset it describes (a file, a table, a URL), `tags` cut across the folder structure, and `timestamp` feeds freshness checks. The [templates](/docs/templates/) page has ready scaffolds. ## Links are edges Concepts reference each other with plain Markdown links: absolute from the bundle root (`/tables/customers.md`) or relative (`../model/graph.md`). Files are nodes, links are edges, so the knowledge graph emerges from the writing itself; you never declare it. Broken links are tolerated by the spec (section 5.3): a link to a concept nobody wrote yet is demand, not an error, and [okf lint](/docs/cli/lint/) ranks those missing targets as a backlog. ## The reserved files Two filenames are structural rather than conceptual: - **`index.md`**, one per directory that wants a map. It lists what lives there and where to descend next, which is how a bundle stays useful past one context window (the spec calls it progressive disclosure; [okf index](/docs/cli/index/) is its read view). A nested `index.md` has no frontmatter; the root one carries only `okf_version`. - **`log.md`**, the bundle's own change history: ISO-dated headings, newest first, one bullet per creation, update, or deprecation. ## Citations Claims that come from somewhere carry a `# Citations` section with numbered links (spec section 8). [okf lint](/docs/cli/lint/) flags external claims without citations and citations that point nowhere. ## Read it as a graph Everything above is plain text, so it diffs, reviews, and greps. When you want the shape instead of the words: [okf server](/docs/cli/server/) renders the live graph, [okf graph](/docs/cli/graph/) prints the raw nodes and edges, and [okf stats](/docs/cli/stats/) sizes the bundle at a glance. --- # The OKF v0.1 specification URL: https://okfgem.com/docs/spec/ Summary: The Open Knowledge Format v0.1 specification: bundle structure, concept documents, cross-linking, index and log files, citations, and the conformance definition. > The Open Knowledge Format specification is authored by Google Cloud Platform and reproduced here under the Apache-2.0 license, Copyright (c) Google LLC. It ships verbatim inside the gem at `lib/okf/skill/reference/SPEC.md`, so the spec your agent reads is the spec you are reading now. # Open Knowledge Format (OKF) **Version 0.1 — Draft** OKF is an open, human- and agent-friendly format for representing *knowledge* — the metadata, context, and curated insight that surrounds data and systems. It is designed to be authored by people, generated by agents, exchanged across organizations, and consumed by both. The format is intentionally minimal: a directory of markdown files with YAML frontmatter. There is no schema registry, no central authority, and no required tooling. If you can `cat` a file, you can read OKF; if you can `git clone` a repo, you can ship it. --- ## 1. Motivation The space of knowledge representation for AI agents is evolving quickly, and many incompatible conventions are emerging. OKF takes the position that knowledge is best represented in commonly accessible, established formats that are: - **Readable** by humans without tooling. - **Parseable** by agents without bespoke SDKs. - **Diffable** in version control. - **Portable** across tools, organizations, and time. The format is minimally opinionated. It standardizes only the small set of structural conventions needed to make a knowledge corpus *self-describing* — anything beyond that is left to the producer. ### Goals 1. Define a universal format that **enrichment agents** can write into. 2. Inform how **consumption agents** should read and traverse it. 3. Facilitate **exchange** of knowledge across systems and organizations. 4. Standardize the small number of **required** fields that must be present for content to be meaningfully consumed. ### Non-goals - Defining a fixed taxonomy of concept types. - Prescribing storage, serving, or query infrastructure. - Replacing domain-specific schemas (Avro, Protobuf, OpenAPI, etc.) — OKF *references* them; it does not subsume them. --- ## 2. Terminology - **Knowledge Bundle** — A self-contained, hierarchical collection of knowledge documents. The unit of distribution. - **Concept** — A single unit of knowledge within a bundle. Represented as one markdown document. May describe a tangible asset (a table, an API), an abstract idea (a metric, a business process), or anything in between. - **Concept ID** — The path of the concept's file within the bundle, with the `.md` suffix removed. For example, `tables/users.md` has concept ID `tables/users`. - **Frontmatter** — YAML metadata block delimited by `---` at the top of a markdown file. - **Body** — Everything in the file after the frontmatter. - **Link** — A standard markdown link from one concept to another, used to express relationships beyond the implicit parent/child hierarchy. - **Citation** — A link from a concept to an external source that supports a claim in the body. --- ## 3. Bundle Structure A bundle is a directory tree of markdown files. The directory structure is independent of the domain — producers organize concepts however makes sense for the knowledge being captured. ``` path/to/bundle/ ├── index.md # Optional. Directory listing for progressive disclosure. ├── log.md # Optional. Chronological history of updates. ├── .md # A concept at the bundle root. └── / # Subdirectories organize concepts into groups. ├── index.md ├── .md └── / └── … ``` A bundle MAY be distributed as: - A git repository (recommended — provides history, attribution, diffs). - A tarball or zip archive of the directory. - A subdirectory within a larger repository. ### 3.1 Reserved filenames The following filenames have defined meaning at any level of the hierarchy and MUST NOT be used for concept documents: | Filename | Purpose | |--------------|--------------------------------------------------------| | `index.md` | Directory listing. See §6. | | `log.md` | Update history. See §7. | All other `.md` files are concept documents. Tags themselves remain a first-class concept — see the `tags` frontmatter field in §4.1. OKF does not specify a separate file format for aggregating documents by tag; producers that want a tag-browsing view can synthesize one at consumption time by scanning frontmatter. --- ## 4. Concept Documents Every concept is a UTF-8 markdown file. It has two parts: 1. A **YAML frontmatter block**, delimited by `---` on its own line at the start of the file and a closing `---` on its own line. 2. A **markdown body**, containing free-form content. ### 4.1 Frontmatter ```yaml --- type: # REQUIRED title: description: resource: tags: [, , …] # Optional timestamp: # Optional last-modified time # … other producer-defined key/value pairs --- ``` **Required:** - `type` — A short string identifying the kind of concept. Consumers use this for routing, filtering, and presentation. Example values: `BigQuery Table`, `BigQuery Dataset`, `API Endpoint`, `Metric`, `Playbook`, `Reference`. Type values are **not** registered centrally. Producers SHOULD pick values that are descriptive and self-explanatory; consumers MUST tolerate unknown types gracefully (typically by treating them as generic concepts). **Recommended (in priority order):** - `title` — Human-readable display name. If omitted, consumers MAY derive a title from the filename. - `description` — A single sentence summarizing the concept. Used by `index.md` generators, search snippets, and previews. - `resource` — A URI that uniquely identifies the underlying asset the concept describes. Absent for concepts that describe abstract ideas rather than physical resources. - `tags` — A YAML list of short strings for cross-cutting categorization. - `timestamp` — ISO 8601 datetime of last meaningful change. **Extensions:** Producers MAY include any additional keys. Consumers SHOULD preserve unknown keys when round-tripping and SHOULD NOT reject documents with unrecognized fields. ### 4.2 Body The body is standard markdown. Producers SHOULD favor structural markdown — headings, lists, tables, fenced code blocks — over freeform prose, since structure aids both human reading and agent retrieval. There are no required body sections. The following section headings have **conventional** meaning and SHOULD be used when applicable: | Heading | Purpose | |----------------|--------------------------------------------------------| | `# Schema` | Structured description of an asset's columns/fields. | | `# Examples` | Concrete usage examples, often as fenced code blocks. | | `# Citations` | External sources backing claims in the body. See §8. | ### 4.3 Example: a concept bound to a resource ```markdown --- type: BigQuery Table title: Customer Orders description: One row per completed customer order across all channels. resource: https://console.cloud.google.com/bigquery?p=acme&d=sales&t=orders tags: [sales, orders, revenue] timestamp: 2026-05-28T14:30:00Z --- # Schema | Column | Type | Description | |---------------|-----------|------------------------------------------| | `order_id` | STRING | Globally unique order identifier. | | `customer_id` | STRING | Foreign key into [customers](/tables/customers.md). | | `total_usd` | NUMERIC | Order total in US dollars. | | `placed_at` | TIMESTAMP | When the customer submitted the order. | # Joins Joined with [customers](/tables/customers.md) on `customer_id`. # Citations [1] [BigQuery table schema](https://console.cloud.google.com/bigquery?p=acme&d=sales&t=orders) ``` ### 4.4 Example: a concept not bound to a resource ```markdown --- type: Playbook title: Incident response — data freshness alert description: Steps to triage a freshness alert on the orders pipeline. tags: [oncall, incident] timestamp: 2026-04-12T09:00:00Z --- # Trigger A freshness alert fires when `orders` lags more than 30 minutes behind its expected SLA. See the [orders table](/tables/orders.md). # Steps 1. Check the [ingestion job dashboard](https://example.com/dash). 2. … ``` --- ## 5. Cross-linking Concepts MAY link to other concepts using standard markdown links. Two forms are supported: ### 5.1 Absolute (bundle-relative) links Begin with `/`, interpreted relative to the bundle root. ```markdown See the [customers table](/tables/customers.md) for the join key. ``` This is the **recommended** form because it is stable when documents are moved within their subdirectory. ### 5.2 Relative links Standard markdown relative paths. ```markdown See the [neighboring concept](./other.md). ``` ### 5.3 Link semantics A link from concept A to concept B asserts a *relationship*. The specific kind of relationship (parent/child, references, joins-with, depends-on, etc.) is conveyed by the surrounding prose, not by the link itself. Consumers that build a graph view typically treat all links as directed edges of an untyped relationship. Consumers MUST tolerate broken links — a link whose target does not exist in the bundle is not malformed; it may simply represent not-yet-written knowledge. --- ## 6. Index Files An `index.md` file MAY appear in any directory, including the bundle root. It enumerates the directory's contents to support **progressive disclosure** — letting a human or agent see what is available before opening individual documents. Index files contain no frontmatter. The body uses one or more sections, each grouping concepts under a heading: ```markdown # Section / Group Heading * [Title 1](relative-url-1) - short description of item 1 * [Title 2](relative-url-2) - short description of item 2 # Another Section * [Subdirectory](subdir/) - short description of the subdirectory ``` Entries SHOULD include the description from the linked concept's frontmatter. Producers MAY generate `index.md` automatically; consumers MAY synthesize one on the fly when none is present. --- ## 7. Log Files (optional) A `log.md` file MAY appear at any level of the hierarchy to record the history of changes to that scope. The format is a flat list of date-grouped entries, newest first: ```markdown # Directory Update Log ## 2026-05-22 * **Update**: Added new BigQuery table reference for [Customer Metrics](/tables/customer-metrics.md). * **Creation**: Established the [Dataplex Playbook](/playbooks/dataplex.md). ## 2026-05-15 * **Initialization**: Created foundational directory structure. * **Update**: Added progressive-disclosure guidelines to the root [index](/index.md). ``` Date headings MUST use ISO 8601 `YYYY-MM-DD` form. Log entries are prose; the leading bold word (`**Update**`, `**Creation**`, `**Deprecation**`, etc.) is a convention, not a requirement. --- ## 8. Citations When a concept's body makes claims sourced from external material, those sources SHOULD be listed under a `# Citations` heading at the bottom of the document, numbered: ```markdown # Citations [1] [BigQuery public dataset announcement](https://cloud.google.com/blog/products/data-analytics/...) [2] [Internal data quality runbook](https://wiki.acme.internal/data/quality) ``` Citation links MAY be absolute URLs, bundle-relative paths, or paths into a `references/` subdirectory that mirrors external material as first-class OKF concepts. --- ## 9. Conformance A bundle is **conformant** with OKF v0.1 if: 1. Every non-reserved `.md` file in the tree contains a parseable YAML frontmatter block. 2. Every frontmatter block contains a non-empty `type` field. 3. Every reserved filename (`index.md`, `log.md`) follows the structure described in §6 and §7 respectively when present. Consumers SHOULD treat all other constraints as soft guidance. In particular, consumers MUST NOT reject a bundle because of: - Missing optional frontmatter fields. - Unknown `type` values. - Unknown additional frontmatter keys. - Broken cross-links. - Missing `index.md` files. This permissive consumption model is intentional: OKF is meant to remain useful as bundles grow, get refactored, and are partially generated by agents. --- ## 10. Relationship to other formats OKF is intentionally close to several established patterns: - **LLM "wiki" repositories** that use markdown + frontmatter as agent-readable knowledge bases. - **Personal knowledge tools** like Obsidian and Notion, which use hierarchical markdown with cross-links. - **"Metadata as code"** approaches that store catalog metadata alongside source code rather than in a separate registry. OKF differs primarily in being **specified** — pinning down the small set of rules needed for interoperability without dictating tooling. --- ## 11. Versioning This document specifies OKF version **0.1**. Future revisions will be versioned in the form `.`: - A **minor** version bump introduces backward-compatible additions (new optional fields, new conventional section headings). - A **major** version bump may make breaking changes (renaming required fields, changing reserved filenames). Bundles MAY declare the OKF version they target by including `okf_version: "0.1"` in a bundle-root `index.md` frontmatter block (the only place frontmatter is permitted in an `index.md`). Consumers that do not understand the declared version SHOULD attempt best-effort consumption rather than refusing the bundle. --- ## Appendix A — Minimal example bundle ``` my_bundle/ ├── index.md ├── datasets/ │ ├── index.md │ └── sales.md └── tables/ ├── index.md ├── orders.md └── customers.md ``` `datasets/sales.md`: ```markdown --- type: BigQuery Dataset title: Sales description: All sales-related tables for the retail business. resource: https://console.cloud.google.com/bigquery?p=acme&d=sales tags: [sales] timestamp: 2026-05-28T00:00:00Z --- The sales dataset contains transactional tables, including [orders](/tables/orders.md) and [customers](/tables/customers.md). ``` `tables/orders.md`: ```markdown --- type: BigQuery Table title: Orders description: One row per completed customer order. resource: https://console.cloud.google.com/bigquery?p=acme&d=sales&t=orders tags: [sales, orders] timestamp: 2026-05-28T00:00:00Z --- # Schema | Column | Type | Description | |---------------|-----------|------------------------------| | `order_id` | STRING | Unique order identifier. | | `customer_id` | STRING | FK to [customers](/tables/customers.md). | | `total_usd` | NUMERIC | Order total in USD. | Part of the [sales dataset](/datasets/sales.md). ``` --- # Your first bundle URL: https://okfgem.com/docs/guides/first-bundle/ Summary: A guided path from an empty folder to a healthy knowledge graph: five concepts, honest frontmatter, cross-links, an index, and the checks that keep it that way. ## Pick the five questions people keep asking Do not start with a migration. Start with the five things your team re-explains every month: why a service exists, what a metric counts, the decision that looks wrong but was deliberate, the runbook everyone asks for, the table nobody dares to touch. Each becomes one file. ## Write the first concept Create a folder (call it `docs/`, `knowledge/`, `.okf/`, anything) and one file per concept. Only `type` is mandatory: ```md --- type: Decision title: Payment id is the dedup key description: Why orders deduplicate on payment_id, not order_id. tags: [billing, incident] timestamp: 2026-07-13T12:00:00Z --- # Overview During the 2023 double-charge incident we found order_id regenerating on retry. payment_id is stable across retries, so [orders](/tables/orders.md) deduplicates on it. Changing this breaks revenue reports. ``` That link to `/tables/orders.md` is allowed to be broken. The spec tolerates it, and the tooling turns it into your backlog. ## Validate early ```bash okf validate docs/ ``` Errors at this stage are almost always a missing frontmatter block or an empty `type`; the [conformance model](/docs/conformance/) lists everything that can actually fail. Warnings are fine, keep going. ## Add the maps when the folder earns them Once a directory holds a handful of concepts, give it an `index.md` (the [templates](/docs/templates/) page has the scaffold; the root one carries `okf_version: "0.1"`). Add a `log.md` and date your changes. These two files are what make the bundle navigable for someone (or some agent) who was not there when it was written. ## Let lint write your backlog ```bash okf lint docs/ ``` The findings are the to-do list: missing concepts ranked by how many places link to them, stubs worth expanding, files nobody can reach. Work the list, or hand it to the [agent skill](/docs/skill/) and review the PRs. When someone asks what the graph looks like: ```bash okf server docs/ ``` ## Make it stick Two habits keep a bundle alive: run [curate](/docs/skill/curate/) (or `validate` + `lint`) whenever the knowledge changes, and wire the checks into [CI](/docs/guides/ci/) so drift shows up as a finding instead of a surprise. In Claude Code, the [plugin](/docs/plugin/) does the first habit for you, after every edit. --- # Gate knowledge drift in CI URL: https://okfgem.com/docs/guides/ci/ Summary: Wire okf validate and okf lint into a pipeline so a broken or rotting bundle fails a check instead of surprising the next reader. Exit codes and a GitHub Actions example. ## The contract Every okf verb keeps the same exit-code contract, which is all CI needs: `0` success, `1` a non-conformant bundle or a crossed lint threshold, `2` usage error. Two gates, two strictness levels: - **[okf validate](/docs/cli/validate/)** fails on illegal OKF (exit `1`). This one belongs in every pipeline; a non-conformant bundle is broken for every consumer. - **[okf lint](/docs/cli/lint/) `--fail-on warn`** fails on curation warnings. Opt into this once the bundle is in decent shape; running it advisory first (no flag) costs nothing. ## GitHub Actions ```yaml name: knowledge on: pull_request: paths: [ "docs/**" ] jobs: okf: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: { ruby-version: "3.3" } - run: gem install okf - run: okf validate docs/ - run: okf lint docs/ --fail-on warn --stale-after 120d ``` The `paths` filter keeps the job off unrelated PRs. `--stale-after 120d` opts into freshness checking; without it, staleness is never reported. ## Tuning the gate - Start advisory: run `okf lint docs/` without `--fail-on` and read the report in the job log until the findings are ones you would actually act on. - Narrow with check names if a category is noisy while you ramp up: `--except missing_timestamp,stub` (names, not category labels; the [curation model](/docs/curation/) lists all sixteen). - `--json` turns either verb into machine-readable output if you want to annotate the PR instead of just failing it. ## What CI cannot check The deterministic gates catch structure: legality and curation shape. Whether the words still match the code is a semantic question, and that belongs to the [maintain verb](/docs/skill/maintain/) of the agent skill, run when the code changes, with `lint --json` as its input. CI catches the rot you can compute; the skill catches the rot you have to read for. --- # Mount the graph in Rails URL: https://okfgem.com/docs/guides/rails/ Summary: The interactive knowledge graph is a Rack app, so it mounts inside a Rails application under any prefix, behind your own authentication. One bundle or a hub of many, auth patterns, the trailing-slash mount contract, and what updates live. ## Why mount it `okf server` is great on a laptop, but a team usually wants the knowledge graph where the team already is: inside the internal Rails app, behind the same sign-in as everything else. The server was built for exactly that. The page under the UI is a plain Rack app, and its endpoints are mount-relative, so it works identically at `/` or under any prefix. ## Add the gem ```ruby # Gemfile gem "okf" ``` `require "okf"` loads only the library: the model, the analyzers, and the on-disk handles. The graph server is a separate, on-demand require, `require "okf/server/app"`, which is exactly how the gem's own CLI loads it. Your app's boot never pays for Rack machinery it does not serve. The [architecture page](/docs/architecture/) explains why the footprint stays this small. ## Mount it in routes ```ruby # config/routes.rb require "okf/server/app" # the server loads on demand, like the CLI does Rails.application.routes.draw do knowledge = OKF::Server::App.new( OKF::Bundle::Folder.load(Rails.root.join(".okf").to_s) ) mount knowledge => "/knowledge" end ``` That is the whole integration. Visit `/knowledge` and you get the same interactive graph as the [public demo](https://demo.okfgem.com): nodes by type, the inspector with rendered Markdown, and the catalog, files, tags, and stats views. The files view is a nested tree of the bundle on disk, with each `index.md` and `log.md` sitting as a row at the top of the folder it documents, and an "Indexes only" toggle when those are all you want to see. ## Many bundles: mount a hub `OKF::Server::App` serves one bundle. To serve several behind one prefix, with an in-page switcher that jumps between them, mount `OKF::Server::Hub` instead. You hand it an ordered list of `OKF::Server::Hub::Bundle` structs (`slug`, `folder`, `title`); the first is the one the prefix root opens. ```ruby # config/routes.rb require "okf/server/hub" Rails.application.routes.draw do bundles = [ OKF::Server::Hub::Bundle.new( "handbook", OKF::Bundle::Folder.load(Rails.root.join(".okf").to_s), "Handbook" ), # ...more bundles... ] mount OKF::Server::Hub.new(bundles) => "/knowledge" end ``` Each bundle is served at `/knowledge/b//`, and `/knowledge` redirects to the default. A plain `Hub.new(bundles)` is read-only: the registry-editing routes (`POST /registry/*`) answer only when you pass both an explicit `writable: true` and a `registry:`, and neither the hub nor the app ever writes to your bundle's Markdown either way. Mounting it exposes reading only, which is what you want inside someone else's app. ## Preserve the trailing slash (the hub's one mount contract) The hub serves each graph at `/knowledge/b//`, with the trailing slash, and 301-redirects the slashless `/knowledge/b/` onto it. That slash is load-bearing, not cosmetic: the page's fetch endpoints are relative (`../../search`, `..//`), so they resolve correctly only when the document itself lives at `.../b//`. Here is the trap. A normalizing host router, Rails' `mount` included, strips the trailing slash from the mounted app's `PATH_INFO` before the hub ever sees it. So a browser request for `/knowledge/b/handbook/` reaches the hub looking slashless, the hub 301s to add the slash, the router strips it again, and the two spin into an infinite redirect loop (`ERR_TOO_MANY_REDIRECTS`). The hub cannot fix this from standard Rack: it has no way to tell "the visitor really typed no slash" (301 is correct) from "the router stripped the slash the visitor sent" (301 is wrong). Only the host still holds that signal, so the host has to restore the slash before delegating. In Rails, wrap the hub in a small middleware: ```ruby class OKFMountAdapter def initialize(app) @app = app end def call(env) request = ActionDispatch::Request.new(env) original = request.original_fullpath.to_s.split("?", 2).first path_info = env["PATH_INFO"].to_s # Append only when the browser's real path ended in "/", so leaf # endpoints like /node and /search are left untouched. if original.end_with?("/") && !path_info.end_with?("/") env["PATH_INFO"] = "#{path_info}/" end @app.call(env) end # Keep `rails routes` and the dev error page readable. A hub's default # inspect walks every loaded bundle, concept, and body, which is # megabytes of output; a terse one keeps the route table usable. def inspect = "#" end mount OKFMountAdapter.new(OKF::Server::Hub.new(bundles)) => "/knowledge" ``` That wrapper is also the natural place to enforce auth: return a 302 to your sign-in route, or a 404, before you call `@app.call(env)`, since a mounted Rack app bypasses your controllers' `before_action`s. The [auth patterns below](#put-your-auth-in-front) apply to the hub as much as to the single-bundle app. ## Put your auth in front The graph inherits whatever stands in front of it, which is the point of mounting instead of running a second service. With Devise: ```ruby authenticate :user do mount knowledge => "/knowledge" end ``` Or, framework-free, wrap it in basic auth: ```ruby protected_graph = Rack::Builder.new do use Rack::Auth::Basic, "Knowledge" do |user, pass| ActiveSupport::SecurityUtils.secure_compare(pass, ENV.fetch("KNOWLEDGE_PASS")) end run knowledge end mount protected_graph => "/knowledge" ``` Anything Rack understands works: Devise, Warden scopes, IP allowlists, your SSO middleware. ## No Rails? Plain Rack works too ```ruby # config.ru require "okf" require "okf/server/app" run OKF::Server::App.new(OKF::Bundle::Folder.load(".okf")) ``` `rackup` serves it with whatever server you already run (Puma, Falcon). The built-in WEBrick runner behind `okf server` is just a convenience wrapper around this same app. ## What updates live, and what does not Concept bodies are fetched from disk on every click, and the Files view re-reads `log.md` every time it is opened, so editing a concept or appending a log entry shows up next time with no restart. The bundle's structure (which files exist, how they link, the authored index maps) is read when `Folder.load` runs, so adding or removing concepts needs an app restart or a fresh folder load, same as any boot-time configuration. One boundary worth respecting in a mounted setup: the page renders bundle content through DOMPurify and escapes everything it inlines, but it still loads its viewer libraries from a CDN and renders whatever links the bundle carries. Serve bundles you trust; the [graph server page](/docs/graph-server/) has the full trust write-up. --- # The OKF agent skill URL: https://okfgem.com/docs/skill/ Summary: The okf skill: a SKILL.md plus playbooks, references, and templates that ship inside the gem and teach any coding agent to author, curate, search, and consume OKF bundles. The okf agent skill is what turns a general coding agent into one that reads and writes the format with judgment. It carries the playbooks the agent follows to produce, maintain, search, and consume a bundle, and it ships inside the gem, so any skill-reading agent picks it up on install. The CLI computes what is true about a bundle; the skill decides what to do about it. ## What it is The gem carries a companion agent skill: a `SKILL.md` plus playbooks, reference files, and templates under `lib/okf/skill/`. Install it into any coding agent that reads skills and the agent becomes the OKF expert in your repository. It knows how to model concepts, keep a bundle in sync with the code, judge its curation quality, retrieve answers from it without reading it whole, and use it as context for real tasks. Because the skill ships inside the gem, installing the gem already puts the skill on your machine, and the skill's CLI reference can never drift from the executable it was released with. The skill carries the judgment; the [`okf` CLI](/docs/cli/) handles the mechanics. That split is the whole design. ## Three lenses The skill teaches the agent to judge a bundle by three separate questions and never conflate them: | Lens | Question | Tool | Nature | |------|----------|------|--------| | Legal | Is it conformant OKF (section 9)? | [`okf validate`](/docs/cli/validate/) | Binary, tolerant | | Good | Is it navigable, complete, fresh? | [`okf lint`](/docs/cli/lint/) | Advisory, structural | | True | Is it consistent and current? | The agent, reasoning over `lint --json` | Semantic, needs meaning | `validate` is forbidden by the spec from failing a bundle over broken links or missing optional fields; that is `lint`'s job. And neither tool can judge contradictions or semantic staleness, the concept that parses fine but no longer matches reality. Only an agent reasoning over meaning can, and that third lens is where the skill earns its keep. The [conformance model](/docs/conformance/) explains why the first two stay separate. ## The division of labor The CLI is the agent's eyes; the agent is the judgment. - **The agent shells out, never eyeballs,** anything a verb computes: conformance, what exists, what links where, what is stale, the map. Every read verb takes `--json`, and the list views filter by type, area, and tag, so the agent asks the narrow question instead of paging through the bundle. - **The agent judges what the CLI cannot:** contradictions, semantic staleness, whether a loose file is terminal by design, whether a singleton tag is a deliberate marker. Tool output is evidence, never a verdict. The checks are deterministic and always run through the executable. The skill never lets the agent reason out conformance by hand. ## The verbs The skill routes eight verbs, each with its own playbook. In Claude Code they run as `/okf:gem `; used standalone, the skill infers the verb from your request. | Verb | What it does | |------|--------------| | [orient](/docs/skill/orient/) | No arguments: read the bundle's state and recommend the highest-value next move, never auto-running one | | [search](/docs/skill/search/) | Answer a question from the bundle, token-lean: map, finder, and only the winning bodies | | [produce](/docs/skill/produce/) | Create or extend a bundle from code, docs, or knowledge in people's heads | | [migrate](/docs/skill/migrate/) | Adopt existing Markdown docs in place: frontmatter and reserved files added, bodies kept verbatim | | [maintain](/docs/skill/maintain/) | Sync the bundle's content with reality after the code or docs change | | [consume](/docs/skill/consume/) | Use the bundle as context for a task, writing back what you learn | | [curate](/docs/skill/curate/) | Structural upkeep as the bundle stands: `validate` + `lint` + `loose` | | [doctor](/docs/skill/doctor/) | Install and verify the CLI, then doctor the bundle | Any [CLI verb](/docs/cli/) works as an argument too: the skill runs it and interprets the output through the three lenses. ## Which target A verb needs a bundle, and since 1.9.0 the skill treats a name as a first-class way to say which one. **A leading `@` is a [registry](/docs/cli/registry/) ref, not a path**: `@slug` names a registered bundle and bare `@` the registry default, so the skill routes straight to `okf @slug` instead of hunting for a directory. `okf search` takes several at once (`@a @b`, or `@all`). A plain path is used as given. Given no target at all and a working directory that carries no bundle, the next move is `okf registry list` rather than a search across sibling directories. Pointed at a directory that holds Markdown but no bundle root, the skill does not grind through the conformance errors: it suggests [migrate](/docs/skill/migrate/) and lets you choose. The CLI's own error teaches the same grammar, so a mistyped path answers with `@slug` addressing instead of leaving you to find it. **The skill also stopped checking whether the CLI is installed.** It used to spend a round on a presence probe before every task; now it runs the verb, and treats a shell "command not found" as the only signal to install. A line that begins `error:` is the CLI answering, a bundle or usage result to read, never a missing toolchain. The two exceptions are the verbs that exist to decide whether to install: orient and [doctor](/docs/skill/doctor/). ## Install it Point the installer at your agent's config directory and the skill settles into its own `skills/okf/` folder: ```bash okf skill .claude # Claude Code -> .claude/skills/okf okf skill .agents # agent-agnostic -> .agents/skills/okf ``` The destination is required; there is no default. The tree lands in `/skills/okf`, unless `` already ends in `skills` (then `/okf`) or in `okf` (used as-is). Pass `--here` to paste the tree straight into ``, wherever it is. The resolved directory must be empty unless you pass `--force`, so a customized skill is never clobbered. [`okf skill`](/docs/cli/skill/) documents the full command. Using Claude Code? The [plugin](/docs/plugin/) carries this same canonical skill, plus the `/okf:gem` command and a post-edit curation hook. ## The flywheel The lifecycle is a flywheel, not phases. [Produce](/docs/skill/produce/) seeds a bundle; [consume](/docs/skill/consume/) reads it; [maintain](/docs/skill/maintain/) runs whenever reality drifts or whenever consuming teaches the agent something durable. That write-back reflex is what keeps a bundle alive instead of rotting into folklore: learn something while consuming, switch to maintain, record it. Curation is continuous, not a phase. [Curate](/docs/skill/curate/) settles structural debt whenever it accumulates, and with the [plugin](/docs/plugin/) active, every edit inside a bundle gets validated and linted on the spot. --- # search: retrieve without reading the whole bundle URL: https://okfgem.com/docs/skill/search/ Summary: How the skill answers a question from a bundle: ingest the index map, decide where to look, cut across with okf search, and read only the winning bodies. ## When to use it - "What do we know about X?", "Where is X documented?", "Which concept covers the invoice dedup key?": a pointed question, not broad task context. - Before pasting a bundle into the context window. The whole point of the format is that you never need to. - When you suspect the knowledge exists but nobody remembers where. Finding it cheaply is what keeps the bundle in the loop at all. ## What the agent does Retrieval is [progressive disclosure](/docs/spec/#6-index-files) end to end. **Every step pays a few hundred bytes to decide what the next step reads**; full bodies come last, and only the winners. 1. **Ingest the map, decide where to look.** [`okf index --no-body`](/docs/cli/index/) is the skeleton: every directory, its counts, types, tags, children. The agent does the semantic matching here. The question names a meaning, the map names areas, and connecting the two is judgment, not string equality. A promising area costs one more call: `okf index --area billing` returns its authored index body and listing. 2. **Cut across with the finder when the question is lexical.** An exact symbol, an error code, a phrase goes to [`okf search`](/docs/cli/search/), scoped by what the map taught: `--area billing --type Decision`. Ranked rows with snippets come back, and often the snippet already answers. 3. **Read only the winners.** A match row's id is its file. The agent reads that one file, follows its links one hop at a time, and checks `log.md` when freshness matters. 4. **Answer with citations, then write back.** The concept ids used are cited in the answer. If the answer was missing, stale, or needlessly hard to find, the agent switches to [maintain](/docs/skill/maintain/) and records what it learned. **Retrieval friction is curation signal.** The division of labor stays sharp: **the CLI is exact and deterministic by default; the agent is the fuzzy layer.** Synonyms and vocabulary drift are handled by reasoning over the map and [`okf tags`](/docs/cli/tags/), because judgment about meaning beats approximate string matching at that job. The tool does carry a `--fuzzy` flag, and it earns its keep on one narrow case: a genuine typo, or a spelling you half remember. Reach for it there, not as a substitute for learning the bundle's own words. ## Try it In Claude Code with the okf plugin: ```text /okf:gem search where do we document the invoice dedup key? ``` Or ask the skill in plain words: "Search the bundle: what do we know about retry idempotency?" ## Pitfalls - **The dump is the anti-pattern.** `okf graph --json` with bodies, or reading the whole tree "for context", costs more than every step of the ladder combined. The gem's own test suite pins the economics: the progressive path must answer a planted question in under a quarter of the bytes of the full dump. - **Grep before map misses what is not there.** Grep cannot find the index entry that is missing, and line hits do not rank. Go through the format's own views first; grep is the fallback when the CLI is absent. - **Synonym hammering.** Mechanically retrying near-identical terms is token spend without judgment. Learn the bundle's vocabulary from its tags and types, then ask in its own words. --- # orient: pick the next move URL: https://okfgem.com/docs/skill/orient/ Summary: How the skill answers 'what should I do?': it reads the bundle's state through the CLI, then recommends the highest-value next verb without running one. ## When to use it - You have a bundle (or think you might) and no clear next step. Orient reads the signals and tells you which verb pays off most right now. - You just picked up a repo with an unfamiliar `.okf/` directory and want a state-of-the-bundle briefing before touching anything. - In Claude Code, `/okf:gem` with no arguments lands here. ## What the agent does Orient is a diagnosis, not a workflow. The agent works down a short ladder of signals and stops at the first one that decides the recommendation: 1. **Is the CLI present?** `okf --version`. If it is missing, the only useful move is setup: the agent recommends [doctor](/docs/skill/doctor/) and stops there. Everything below needs the CLI. 2. **Is there a bundle?** The directory you named, else a `.okf/` directory, else a root `index.md` whose frontmatter carries `okf_version`. No bundle means nothing to curate, maintain, or consume yet, so the recommendation is [produce](/docs/skill/produce/): create the first bundle from the code, the docs, or what lives only in people's heads. 3. **What state is it in?** The agent measures instead of eyeballing: [`okf validate --json`](/docs/cli/validate/), [`okf lint --json`](/docs/cli/lint/), [`okf loose --json`](/docs/cli/loose/). Then it recommends by what they report, most blocking first: - `validate` reports errors: lead with [curate](/docs/skill/curate/). Section 9 conformance errors are the only hard failures, and they get fixed before anything else. - clean `validate` but `lint` or `loose` findings: still [curate](/docs/skill/curate/), to settle the curation debt, naming the top one or two categories from the report. - clean across the board: the bundle is healthy, so lead with [consume](/docs/skill/consume/) and offer [maintain](/docs/skill/maintain/) for when the code or docs have since changed. If `git status` shows uncommitted changes to code the bundle describes, prefer maintain: that is exactly the drift it exists to close. 4. **A freshness caveat.** If the bundle carries timestamps, the agent notes that a plain `lint` said nothing about staleness, and that `okf lint --stale-after 90d` is the check that would. The output is two or three pointed picks, each with the exact command to run and a one-line reason drawn from the signals. The agent never runs a workflow from here; the choice stays with you. ## Try it In Claude Code with the okf plugin, run `/okf:gem` with no arguments. Without the plugin, ask the skill in plain words: ```text I have an OKF bundle in docs/. What is the highest-value thing to do with it right now? ``` ## Pitfalls - **Orient recommends but never auto-runs.** If the agent starts fixing lint findings from here, it has skipped the handoff; the fixing belongs to [curate](/docs/skill/curate/) after you pick it. - **The signals come from the CLI, not from skimming files.** An agent that opens a few concepts and calls the bundle "fine" has not oriented; the three `--json` measurements are the diagnosis. - **A quiet report is not "nothing to do".** Freshness is off by default, so a clean `lint` says nothing about stale concepts, and a healthy bundle is exactly the one worth consuming. --- # produce: create or extend a bundle URL: https://okfgem.com/docs/skill/produce/ Summary: How the skill creates or extends an OKF bundle: model concepts from code, docs, or tribal knowledge, then walk the closeout gate before finishing. ## When to use it - The repo has no bundle yet and carries knowledge worth keeping: services, APIs, schemas, metrics, runbooks, decisions. - The bundle exists but a whole new area belongs in it: a new service, a data pipeline, a set of decisions that so far live only in people's heads. - "Document this in OKF" and "capture X as a concept" both route here. ## What the agent does Produce is the modelling verb, and most of it is judgment the CLI cannot make. The playbook keeps the steps short and defers the craft (granularity, choosing `type`, tag vocabulary, topology, links, citations) to the skill's authoring reference, which the agent reads before any non-trivial produce. 1. **Pick the sources.** Code (source files, READMEs, docstrings, config), existing docs or wiki pages (distilled into concepts, with the originals cited under a `# Citations` heading), or manual knowledge: the decisions, playbooks, and metrics that exist only in people's heads. 2. **Choose a domain-based layout.** Directories say what the knowledge is about (`services/`, `datasets/`, `decisions/`), never group by concept type. One concept per file, where a concept is the smallest unit of knowledge someone would link to or cite on its own. 3. **Write each concept from the template.** A descriptive `type` drawn from the bundle's existing vocabulary, recommended fields filled (`title`, `description`, `tags`, `timestamp`), and cross-links written into prose: the sentence around a link is what names the relationship, because links themselves are untyped on purpose. Before minting a tag, the agent runs [`okf tags`](/docs/cli/tags/) and reuses the existing vocabulary first. The [templates](/docs/templates/) page shows the shapes it starts from. 4. **Index and log.** An `index.md` per directory, added or refreshed; the bundle root gets an `index.md` whose frontmatter carries only `okf_version: "0.1"`; and a dated entry lands in `log.md`. 5. **Close out.** Before calling the work done, the agent walks the closeout gate: every index enumeration matches reality (it re-runs [`okf index`](/docs/cli/index/) and checks each listing), `log.md` has its entry, timestamps are set, [`okf validate`](/docs/cli/validate/) shows zero errors, and the cheap [`okf lint`](/docs/cli/lint/) findings are cleared. What stays with the agent throughout: deciding what is one concept versus two, what type vocabulary fits the bundle, which relationships deserve a link, and what is worth writing down at all. The CLI checks the result; it cannot make those calls. ## Try it In Claude Code with the okf plugin, run `/okf:gem produce`, or ask the skill in plain words: ```text Document the billing service in OKF: the API, the retry queue, and the decision to drop the legacy payment provider. ``` ## Pitfalls - **Do not restate what the code already says.** A bundle that mirrors function signatures goes stale on the next commit and adds no knowledge. Capture the why, the cross-cutting relationships, the tradeoffs; link to the code for the rest. - **A new type per file makes `type` meaningless.** The graph is grouped and colored by type, so a small reused vocabulary is what keeps it legible. Check what the bundle already uses before inventing one. - **No placeholder `resource` URIs.** `resource` marks a concept that is a real, addressable asset, and it is what lets [maintain](/docs/skill/maintain/) find every concept a changed asset touches. On abstract concepts (a decision, a principle), omitting it is meaningful, not lazy. - **The closeout gate is not optional.** Skipping the index re-check is how enumeration drift starts, and grep cannot find the entry that should be there but is not. --- # migrate: OKFy existing docs in place URL: https://okfgem.com/docs/skill/migrate/ Summary: How the skill converts documentation you already have into a conformant OKF bundle: frontmatter and reserved files added, every body kept verbatim, with the validator as the worklist. ## When to use it - A directory already holds Markdown documentation and you want it to become a bundle: a `docs/` folder, a wiki export, a pile of ADRs. - "Convert these docs", "migrate this folder", "OKFy our documentation" all route here. - Any verb pointed at a directory that is not a bundle yet (Markdown files, but no root `index.md` carrying `okf_version`) suggests this one rather than working through the conformance errors. The boundary with [produce](/docs/skill/produce/) is the whole reason migrate exists. Produce distills sources into new concepts, which is right for code, wikis you want condensed, and knowledge that lives only in people's heads. Migrate is for documents that already *are* the knowledge: they survive as the concepts themselves. [Turn the docs you already have into a bundle](/blog/okf-migrate-existing-docs/) walks a real migration. ## What the agent does The rule that governs every step: **bodies are sacred.** Migrate never rewrites, reorders, or summarizes a body. Besides prepending frontmatter, the only edit it may make is repointing a relative link that a file move broke. 1. **Inventory from the validator, not by eyeballing.** [`okf validate --json`](/docs/cli/validate/) enumerates every file missing frontmatter or `type`, and every malformed reserved file. That list is the worklist, and the pass is done when it reports zero. 2. **Prepend frontmatter; leave the body alone.** A small `type` vocabulary derived from what the documents are (reusing before minting, checked with [`okf types`](/docs/cli/types/)), `title` and `description` from each document's own heading and purpose line, `timestamp` from the document's own date when it carries one, and tags only where they connect concepts that type and directory do not already group. 3. **Keep the topology.** The directory tree is already domain knowledge, so it stays. One file is one concept by default. When a file shows split signals (two types fighting for the frontmatter, two audiences), the agent flags it for a later [curate](/docs/skill/curate/) pass instead of splitting it now. 4. **Add the reserved files.** A root `index.md` whose frontmatter carries only `okf_version: "0.1"`, a nested `index.md` per directory, and a `log.md` with a dated Creation entry naming where the documents came from. 5. **Check the links.** The relative links already in your documents are the graph's edges. The agent verifies they resolve inside the bundle and repoints only what a move broke. Links pointing outside the bundle are tolerated by the spec and stay as they are. 6. **Close out, then prove it.** The usual gate ([`validate`](/docs/cli/validate/) clean, [`lint`](/docs/cli/lint/), [`loose`](/docs/cli/loose/), tag review, index re-check), plus the promise migrate makes: each concept, with its frontmatter block stripped back off, is byte-identical to the source document. ## Try it In Claude Code with the okf plugin, run `/okf:gem migrate docs/`, or ask the skill in plain words: ```text Turn the docs/ folder into an OKF bundle. Keep the bodies exactly as they are. ``` ## Pitfalls - **Migration makes docs legal, not true.** A concept that was wrong before is now a wrong concept with tidy frontmatter. Conformance and correctness are different questions; [`lint`](/docs/cli/lint/) will tell you what is thin, stale, or uncited, and only you can tell what is false. - **Do not restructure while migrating.** Splits, renames, and retyping belong to a later [curate](/docs/skill/curate/) or [maintain](/docs/skill/maintain/) pass. Rearranging documents in the same breath that converts them makes the diff unreviewable. - **A new type per file makes `type` meaningless.** The temptation is worse here than in produce, because the documents arrive in every shape at once. Derive a small vocabulary from what they are, then reuse it. - **The missing concepts are a feature.** Links pointing at documents nobody wrote show up in lint's backlog, ranked by demand. That is the day-1 worklist for [produce](/docs/skill/produce/), not a defect in the migration. --- # maintain: sync knowledge with reality URL: https://okfgem.com/docs/skill/maintain/ Summary: How the skill catches a bundle up after the code or docs change: find every affected concept, update it, and verify that nothing drifted silently. ## When to use it - The project changed (a rename, a migration, a retired service) and the bundle's content must catch up with reality. - You learned something durable while [consuming](/docs/skill/consume/) the bundle: a fact it lacks, a link it is missing, a concept that no longer matches what shipped. - Note what maintain is not: a bundle that is structurally messy but factually current wants [curate](/docs/skill/curate/) instead. ## What the agent does 1. **Orient before hunting.** [`okf index`](/docs/cli/index/) maps the whole bundle in one pass (every directory's index body, rollups, and listings), `log.md` gives the baseline of what changed last, and [`okf stats`](/docs/cli/stats/) shows size and shape. This comes before any grep, because grep cannot find an index entry that is missing: only the map shows what a listing should contain but does not. 2. **Find every affected concept.** The classic failure is fixing only the obvious one. The agent runs [`okf search`](/docs/cli/search/) with the changed asset's `resource` URI, path, and name (it hits frontmatter and bodies and returns ranked concept ids, with grep as the backstop for what search cannot express), and pulls [`okf graph --json --minimal`](/docs/cli/graph/) for the edges, the concepts that link to the ones being touched, without paying for every body. Search and the graph do the finding, so nothing drifts silently. 3. **Update.** Bodies and timestamps, fixed or added cross-links, new concepts for new assets. Retired assets get a deprecation note rather than silent deletion, so the context that explains them survives. 4. **Update every enumeration.** A new, renamed, or removed concept changes its directory's `index.md` listing too, not just the concept file. A dated entry goes into `log.md`, and the agent re-runs `okf index` to confirm each listing matches reality. 5. **Check.** [`okf validate`](/docs/cli/validate/), then [`okf lint`](/docs/cli/lint/) for the curation drift the change introduced: new orphans, broken citations, dangling index entries. When concepts carry timestamps, `--stale-after` (for example `90d`) goes on the lint, because freshness is off by default. 6. **Review loose files.** [`okf loose`](/docs/cli/loose/) lists the concepts with no cross-links in or out. This pass is semantic, so the tool only surfaces the set; for each floater the agent judges intent: should it link out (write the sentence that explains the relationship and put the link in it), should something link to it, or is it terminal by design, like a backlog item or a leaf reached only through its index, which is not a defect. 7. **Curate the tag vocabulary** when the pass touched tags or [`okf tags`](/docs/cli/tags/) shows a long tail of singletons. Reading the grouped views (`--by area`, `--by type`), the agent merges twins (two tags riding the exact same concepts), drops group-name echoes (a tag restating an axis the concept already carries), questions each singleton rather than treating a count of 1 as a verdict, and protects the connective tags consumers have already learned. Before calling the pass done, the agent walks the same closeout gate [produce](/docs/skill/produce/) ends with, as the check that none of the steps above was skipped. ## Try it In Claude Code with the okf plugin, run `/okf:gem maintain`, or ask the skill in plain words: ```text We renamed the orders table to sales_orders and split checkout out of the storefront service. Update the knowledge bundle to match. ``` ## Pitfalls - **Fixing only the obvious concept.** The concept named after the changed asset is rarely the only one that mentions it. Let the `resource` search and the graph find the rest; reading the whole bundle only scales on tiny ones. - **Enumeration drift.** Updating the concept but not its directory's `index.md` leaves a listing that lies. Grep cannot catch this, which is why the playbook re-runs `okf index` and eyeballs each listing. - **A quiet lint is not a fresh bundle.** Staleness reporting is off by default; without `--stale-after`, lint will not tell you what the change left stale. - **Zero loose files is not the goal.** Terminal-by-design leaves are fine, and forcing links onto them adds noise, not knowledge. Loose is a review list, and the judgment is the point. --- # consume: use a bundle as context URL: https://okfgem.com/docs/skill/consume/ Summary: How the skill uses a bundle as task context: orient on the index and log, follow links into what the task needs, and write back what the work teaches. ## When to use it - A repo carries a bundle and your task needs its knowledge: planning a migration, touching a service the bundle describes, answering "how does this system work here". - Before grepping the codebase for tribal knowledge someone already wrote down. - In practice this verb often routes itself: a repo with a bundle plus a task needing its knowledge is consume, no invocation required. ## What the agent does Consume is deliberately the shortest playbook, because a well-curated bundle does most of the work: 1. **Orient first.** [`okf index`](/docs/cli/index/) maps the whole bundle in one pass: every directory's index body, rollups, and listings. `log.md` gives recent history. This is the skill's always-on reflex: the map and the log come before grep or opening leaf files, because they are the cheapest high-signal context there is. 2. **Follow links, not directories.** From the map, the agent follows cross-links only into the concepts the task actually needs, and [`okf search`](/docs/cli/search/) answers the pointed questions along the way in ranked rows. On a large bundle, [`okf graph --json --minimal`](/docs/cli/graph/) hands over the whole link structure at once without carrying every body, so a traversal can be planned without opening any file, and the list views ([`okf catalog`](/docs/cli/catalog/), [`okf files`](/docs/cli/files/)) filter by type, area, and tag for the narrow questions. 3. **Tolerate broken links.** A link to a concept that does not exist yet is not-yet-written knowledge, not an error. The [spec](/docs/spec/) requires consumers to tolerate it, and lint's backlog view treats it as demand. 4. **Write back.** If the task teaches something durable (a fact the bundle lacks, a link it is missing, a concept that no longer matches reality), the agent switches to [maintain](/docs/skill/maintain/) and records it before finishing. The CLI serves the views; the judgment is in choosing which concepts matter for the task and in recognizing, mid-task, the moments worth writing back. ## Try it In Claude Code with the okf plugin, run `/okf:gem consume`, or ask the skill in plain words: ```text Use the knowledge bundle in .okf/ to plan the migration off the legacy auth service. ``` ## Pitfalls - **Consume writes back what it learns.** Ending the task without recording the durable learning is the failure that lets bundles rot into folklore. If the work taught it, the bundle should carry it; that write-back reflex is the whole flywheel. - **Skipping orientation.** Grepping straight into leaf files misses what the index would have shown in one pass, and it can never reveal the index entry that is missing. - **Treating broken links as failures.** They are demand signals for knowledge not yet written. Note them; do not "fix" them by deleting the link. --- # curate: the structural upkeep cycle URL: https://okfgem.com/docs/skill/curate/ Summary: How the skill runs the full curation cycle over a bundle: measure with validate, lint, and loose, fix what hurts readers most, and re-measure the result. ## When to use it - After a burst of edits, or on a schedule: settle the curation debt across reachability, backlog, completeness, and hygiene before it compounds. - When [orient](/docs/skill/orient/) reports `validate` errors or `lint` findings; curate is the verb that acts on them. - Not when the content stopped being true. Curation is structural upkeep of the bundle as it stands; content that no longer matches reality is [maintain](/docs/skill/maintain/)'s job. ## What the agent does 1. **Locate the bundle.** The directory you named, else a `.okf/` directory, else a root `index.md` whose frontmatter carries `okf_version`. If the `okf` CLI is missing, the agent stops and follows [doctor](/docs/skill/doctor/) first; nothing below works without it. 2. **Measure.** Three deterministic reads: [`okf validate --json`](/docs/cli/validate/), [`okf lint --json`](/docs/cli/lint/), and [`okf loose --json`](/docs/cli/loose/). 3. **Interpret through the three lenses, kept separate.** Conformance errors (section 9) are the only hard failures and get fixed first, always. Lint findings are advisory curation debt, ranked by how much each hurts a reader navigating the graph, not by raw count. Loose files can be legitimate terminal leaves, so each one gets judged before it gets linked anywhere. The [curation model](/docs/curation/) describes the categories lint reports across. 4. **Propose, then apply.** The agent lists the fixes worth making in order: must-fix errors first, then the debt worth settling, then the judgment calls. It applies the ones you confirm, or all the obvious ones when you asked it to just clean up. 5. **Re-measure.** `validate` and `lint` run again, and the report is the before and the after in two lines. The measuring is entirely the CLI's; the ranking, the loose-file judgment, and the call on which debt is worth settling stay with the agent. In Claude Code with the [plugin](/docs/plugin/), a smaller version of this cycle also runs automatically after every edit inside a bundle. ## Try it In Claude Code with the okf plugin, run `/okf:gem curate`, or ask the skill in plain words: ```text Run a curation pass over docs/: fix any conformance errors and settle whatever curation debt is worth it. ``` ## Pitfalls - **Curate is structural; maintain is semantic.** When curation surfaces a concept that parses fine but no longer matches reality, that is a maintain job, and the agent switches verbs for those concepts instead of patching them structurally. - **Do not chase zeros.** Lint is advisory by design. Driving every count to zero can make a bundle worse: forced links on terminal leaves, deleted tags instead of a curated vocabulary. Rank by reader pain, fix what earns it. - **Loose is a review list, not a to-do list.** A backlog item or a leaf reached only through its index can be loose by design, and that is not a defect. - **Errors always come first.** Section 9 conformance failures are the only findings that block everything else; no amount of tidy tagging matters while `validate` is red. --- # refine: restructure the bundle to get the most from OKF URL: https://okfgem.com/docs/skill/refine/ Summary: How the skill optimizes a bundle's shape rather than its content: measure tag locality and hub origins with the CLI, judge where knowledge should live, and propose the moves. Refine changes where knowledge lives, never what it says, and it proposes rather than auto-applies. ## When to use it - When the content is right but the **shape** may not be: directories grown fat by one additive pass after another, hubs homed by history rather than meaning, a tag layer that never became the second index it could be. - "Restructure this", "rebalance the bundle", "is the structure right", "get more out of OKF" all route here. - It is the third authoring boundary, and the one most often confused with the other two. [curate](/docs/skill/curate/) keeps the structure sound **as it stands**; [maintain](/docs/skill/maintain/) keeps the content **true**; refine changes **where knowledge lives**, never what it says. ## What the agent does The frame that governs every move: the directory tree is a lossy projection of the link graph. A tree gives each concept one parent, so it can encode only the single dominant decomposition; every genuinely many-to-many relationship rides links and tags, never a new directory. And cohesion outranks balance, always: a move has semantic cost, so evenness is a tiebreaker and a fatness alarm, never the goal. 1. **Orient.** [`okf dirs`](/docs/cli/dirs/) for the shape and, through its `subtree` count, where the weight sits; `log.md` for how the bundle grew; [`okf stats`](/docs/cli/stats/) for the totals. Additive growth optimizes each pass locally and never the whole, and that is the drift this verb corrects. 2. **Measure, because the CLI is the evidence.** Baseline [`validate`](/docs/cli/validate/), [`lint --stale-after`](/docs/cli/lint/), and [`loose`](/docs/cli/loose/) first, since refine assumes a sound bundle and hard errors are curate's job. Then two structural reads: [`okf tags --by dir`](/docs/cli/tags/), whose `count/total` makes a tag's **locality** legible (a tag wholly inside one directory names a domain, one spread across directories names a concern), and [`okf graph --hubs`](/docs/cli/graph/), which ranks concepts by inbound links and groups those links by source directory, the **origin test** for every hub. 3. **Diagnose, because you are the judgment.** The measurements are evidence, never verdicts. A concern never becomes a container: a directory built around a spread tag prunes nothing, so the cross-cut stays a tag. A directory must prune: knowing "it is in there" should eliminate a large, nameable slice. The hub origin test decides moves: an inbound majority from a hub's own directory means leave it, a dominant foreign directory names the better home, and a foreign majority with no dominant source is a shared primitive. A fat directory (roughly 20 to 25 concepts and up) is an alarm, not a rule: it wants heading sections inside its own `index.md` first, and directory nesting pays only at hundreds of concepts. 4. **Propose, never auto-apply.** Refine hands back a report plus a frozen execution prompt; the moves happen when you run them, not before. It prefers the free levers (index sections, tags, links, the connective sentence a link lives in) over file moves, and it never rewrites a body, because summarizing or correcting content is maintain's job reached by switching verbs. ## Try it In Claude Code with the okf [plugin](/docs/plugin/), run `/okf:gem refine`, or ask the skill in plain words: ```text Is docs/ structured well? Look at where the weight sits and whether any hubs are homed in the wrong place, and propose what to move. ``` ## Pitfalls - **Refine proposes; it does not apply.** You get a report and a frozen execution prompt, and the moves land only when you run them. That is deliberate: a structural move has cost, and the call stays yours. - **Cohesion over balance.** A fat directory is an alarm, not a mandate to split. Reach for heading sections in its `index.md` before sub-directories, and nest directories only at hundreds of concepts, where the index headings already form separable groups. - **A concern stays a tag.** A cross-cut like "everything async" prunes nothing, so it never earns a directory, however many concepts carry it. The tag layer is the second index; that is where cross-cuts belong. - **Refine is structural; correcting a body is [maintain](/docs/skill/maintain/).** Refine moves, sections, retags, and relinks. The moment a concept no longer matches reality, that is a maintain job, reached by switching verbs rather than stretching this one. - **Refine assumes a sound bundle.** Run [`validate`](/docs/cli/validate/) and [`lint`](/docs/cli/lint/) first; clearing hard errors is [curate](/docs/skill/curate/)'s job, not this one. --- # doctor: install and verify URL: https://okfgem.com/docs/skill/doctor/ Summary: How the skill installs and verifies the okf CLI on whatever Ruby the system already ships, then checks the repo's bundle and reports on its health. ## When to use it - First contact: a machine or repo that has never run `okf`. Every other verb assumes the CLI is installed; doctor is the one playbook that does not. - The toolchain misbehaves: the gem installed but the command is not found, or you want a quick health read on the bundle. - [orient](/docs/skill/orient/) routes here on its own when `okf --version` fails. ## What the agent does Doctor works through four stations in order, reports what it finds at each, and asks before any install that touches your system. 1. **Is the CLI already here?** `okf --version`. If a version prints, the agent skips straight to the bundle check. 2. **Install the gem.** The agent finds a Ruby first: anything 2.4 or newer works, so whatever the OS or a version manager already ships is enough (`ruby --version`, then `rbenv versions`, `asdf list ruby`, `mise ls ruby` if absent). In a repo with a Gemfile where okf belongs to the project, it uses `bundle add okf` and `bundle exec okf` from then on; otherwise `gem install okf`. It verifies with `okf --version` before moving on, and it knows the failure modes in order of likelihood: - `okf: command not found` right after a successful install means the gem bindir is not on PATH. `ruby -e 'puts Gem.bindir'` shows where the executable landed; a PATH entry or a shim refresh (`rbenv rehash`, `asdf reshim ruby`) fixes it. - A permission error on the system Ruby never gets sudo. `gem install --user-install okf` plus `$(ruby -e 'puts Gem.user_dir')/bin` on PATH does the same job safely. - On Windows, a RubyInstaller Ruby works; the executable is `okf.bat` under the gem bindir, run from the same shell that has `ruby` on PATH. 3. **Doctor the bundle.** The agent locates a bundle: the target you named, which can be a directory or a [registry](/docs/cli/registry/) reference (`@slug` for a registered bundle, a bare `@` for the registry default), else `.okf/`, else a root `index.md` carrying `okf_version`. It then runs [`okf validate`](/docs/cli/validate/) and [`okf lint`](/docs/cli/lint/), and summarizes in a few lines: conformant or not (with the errors if not), the warning count, and the top curation findings by category. Nothing in the cwd? The next move is [`okf registry list`](/docs/cli/registry/), since the bundles you work with are usually registered rather than sitting in a sibling directory. Still nothing, and it offers to bootstrap one through [produce](/docs/skill/produce/), scaffolding nothing without your yes. 4. **Say what changes now.** A short orientation on the new state. With the [Claude Code plugin](/docs/plugin/) active, every Write or Edit inside a bundle runs `validate` and `lint` automatically and returns the findings as context, and `/okf:gem curate` runs the full [curation cycle](/docs/skill/curate/) on demand. Without the plugin, the skill itself instructs the agent to run the same checks after editing a bundle. ## Try it In Claude Code with the okf plugin, run `/okf:gem doctor`, or ask the skill in plain words: ```text Set up the okf CLI on this machine and check the health of the knowledge bundle in this repo. ``` ## Pitfalls - **Doctor asks before installing.** Anything that touches your system (a gem install, a PATH change) waits for a yes; the read-only checks it just runs. - **Never sudo.** A permission error on the system Ruby means `--user-install`, not elevated privileges. - **"Installed but not found" is a PATH problem,** not a broken gem. The fix is `Gem.bindir` on PATH or a shim refresh, not a reinstall. - **No bundle is not a failure.** Doctor reports the absence and offers produce; it never scaffolds a bundle unasked. --- # The okf CLI URL: https://okfgem.com/docs/cli/ Summary: One executable that judges, reads, and serves OKF bundles: conformance and curation gates, ranked search, the read views, a live graph server, and a skill installer. ## One executable, three kinds of verbs `okf` groups its verbs by the kind of question they answer. **Judge verbs** assess a bundle. [`okf validate`](/docs/cli/validate/) is the hard gate: is this a legal OKF bundle under [section 9 of the spec](/docs/spec/#9-conformance)? [`okf lint`](/docs/cli/lint/) is the advisory report: is it well curated, navigable, trustworthy? [`okf loose`](/docs/cli/loose/) is a focused lens over one lint check: which files float in the graph with no cross-links at all? The [conformance model](/docs/conformance/) explains why validation and curation stay separate. **Read verbs** answer questions about what a bundle contains, without a browser. [`okf search`](/docs/cli/search/) is ranked text retrieval across metadata and bodies: which concept covers X, answered in a few rows, by a literal scan out of the box or an opt-in BM25+ index (`--engine index`, and `--fuzzy` for typo tolerance). [`okf dirs`](/docs/cli/dirs/) is the first-glance shape: one row per directory, however big the bundle. [`okf index`](/docs/cli/index/) is the orientation map, and the only view that shows `index.md` files. [`okf catalog`](/docs/cli/catalog/), [`okf files`](/docs/cli/files/), [`okf tags`](/docs/cli/tags/), and [`okf types`](/docs/cli/types/) enumerate concepts by metadata, folder, tag, and type. [`okf stats`](/docs/cli/stats/) sizes the bundle in one screen. [`okf graph`](/docs/cli/graph/) dumps the raw node and edge structure. **Serve and setup verbs** stand things up. [`okf registry`](/docs/cli/registry/) keeps a persistent, per-user list of bundles, so any verb can name one as `@slug` from anywhere. [`okf server`](/docs/cli/server/) boots the interactive graph in your browser, over one bundle, several, or the whole registry behind one hub. [`okf render`](/docs/cli/render/) writes that same graph as one static, self-contained HTML file you can host anywhere, no server needed. [`okf skill`](/docs/cli/skill/) installs the companion agent skill that teaches a coding agent to drive everything above. **Extensions add verbs.** An installed `okf-*` gem can register its own verb through the plugin seam, and `okf help` lists it under installed extensions. The first one shipped is [`okf mcp`](/docs/mcp/) from the `okf-mcp` gem: it serves your registered bundles over the Model Context Protocol, so any MCP-capable host reads them without a shell. ## Invocation conventions Every verb takes the bundle as its one positional argument: `okf [flags]`. - **`@slug` goes wherever a `` goes.** A bundle registered with [`okf registry set`](/docs/cli/registry/) is nameable from any directory: `okf lint @handbook`, `okf render @ -o graph.html` (a bare `@` is the registry default). [`okf search`](/docs/cli/search/) also takes several `@slug`s, or `@all` for every registered bundle at once. - **`--json` is compact by design.** Every emitting verb prints single-line JSON, the token-efficient substrate an agent or a script consumes. `--pretty` indents it for a human and implies `--json`; the bytes differ, the JSON is identical, so parse either. - **`--fields` / `--except` project the JSON** on `search`, `index`, `catalog`, and `files`: `--fields a,b` keeps only those properties, `--except a,b` drops them. They are mutually exclusive, both imply `--json`, and an unknown name is a usage error that lists the valid ones. Projection happens before emission, so you never pay for a field you dropped. - **Plain text is lighter for scanning.** The default views print each key once, not per row, so when you only need to read a bundle rather than extract structure, skip `--json` entirely. ## Exit codes - `0`: success. - `1`: a non-conformant bundle (`validate`), or a `lint --fail-on` threshold crossed. - `2`: usage error (missing directory, unknown flag, unknown check or field name). `graph`, `server`, and `render` are best-effort under [section 9](/docs/spec/#9-conformance): a file with invalid frontmatter is skipped and noted on stderr, never fatal, so one bad file cannot take down the rest. The [CI guide](/docs/guides/ci/) shows how to turn the codes into a pipeline gate. ## All verbs | Verb | Question it answers | Docs | | ---- | ------------------- | ---- | | `validate` | Is this a legal OKF bundle? | [okf validate](/docs/cli/validate/) | | `lint` | Is it well curated? | [okf lint](/docs/cli/lint/) | | `loose` | Which files float in the graph? | [okf loose](/docs/cli/loose/) | | `search` | Which concept covers X? | [okf search](/docs/cli/search/) | | `dirs` | What shape is the bundle, at a glance? | [okf dirs](/docs/cli/dirs/) | | `index` | How is the bundle mapped for a reader? | [okf index](/docs/cli/index/) | | `catalog` | What concepts are here, in detail? | [okf catalog](/docs/cli/catalog/) | | `files` | How is it laid out on disk? | [okf files](/docs/cli/files/) | | `tags` | What themes dominate? | [okf tags](/docs/cli/tags/) | | `types` | What kinds of knowledge does it hold? | [okf types](/docs/cli/types/) | | `stats` | How big and what shape is it? | [okf stats](/docs/cli/stats/) | | `graph` | What are the raw nodes and edges? | [okf graph](/docs/cli/graph/) | | `registry` | Can I name my bundles machine-wide? | [okf registry](/docs/cli/registry/) | | `server` | Can I explore it visually? | [okf server](/docs/cli/server/) | | `render` | Can I host it as a static file? | [okf render](/docs/cli/render/) | | `skill` | Can my agent learn to do all this? | [okf skill](/docs/cli/skill/) | | `mcp` | Can any MCP host read my bundles? | [MCP server](/docs/mcp/), ships with the `okf-mcp` gem | --- # okf validate URL: https://okfgem.com/docs/cli/validate/ Summary: Check OKF v0.1 conformance exactly as the spec defines it (section 9): hard errors for what must hold, warnings for everything the spec tolerates. Exit codes ready for CI. ## When to use it - Before you trust a directory as a bundle: after cloning, after a big edit, before a PR merges. - In CI, as the conformance gate. It exits `1` on a non-conformant bundle, `0` otherwise. - As the first half of the quality question. `validate` answers "is it legal?"; [`okf lint`](/docs/cli/lint/) answers "is it well curated?". The [conformance model](/docs/conformance/) explains why those stay separate. ## How it works `validate` implements the spec's [section 9 conformance definition](/docs/spec/#9-conformance) exactly. Three conditions are hard errors, and the bundle is non-conformant until every one is fixed: 1. **9.1**: every non-reserved file has a parseable YAML frontmatter block; 2. **9.2**: every such block has a non-empty `type`; 3. **9.3**: any `index.md` / `log.md` present follows the reserved-file rules: a nested `index.md` has no frontmatter, a root `index.md` carries only `okf_version`, and `log.md` date headings are ISO `YYYY-MM-DD`. Everything the spec marks as soft guidance is a warning and never fails the bundle: missing recommended fields, non-list tags, an unparseable timestamp, and broken cross-links, which section 5.3 explicitly tells consumers to tolerate. Fix warnings when it is cheap. Never block on them. ## Try it ```bash okf validate docs/ ``` ```text OKF v0.1 conformance — docs concepts: 37 index.md: 10 log.md: 1 ! warn features/link-suggestions.md: cross-link target not found: `/graph-view.md` (tolerated under §5.3) ✓ conformant (33 warning(s)) ``` For CI or an agent, `--json` emits the same result as compact single-line JSON: ```bash okf validate docs/ --json ``` ## Pitfalls - **Do not expect broken links to fail validation.** They are warnings by design; the spec forbids a validator from rejecting them. If dead links should block your pipeline, gate on [`okf lint --fail-on warn`](/docs/cli/lint/) instead. - **A directory with zero frontmatter files is not "invalid", it is empty.** Start with one concept file and validate again. - **Reserved files play by different rules.** If validation fails on an `index.md`, the fix is usually removing frontmatter from a nested index or trimming a root index down to `okf_version`. The [bundle anatomy](/docs/bundle-anatomy/) page shows both shapes. --- # okf lint URL: https://okfgem.com/docs/cli/lint/ Summary: Report curation quality across six categories (reachability, backlog, completeness, freshness, provenance, hygiene) without ever failing conformance. ## When to use it - After [`okf validate`](/docs/cli/validate/) passes. `validate` answers "is it legal?"; `lint` answers the complementary question: is it well curated, navigable, trustworthy? The [curation model](/docs/curation/) explains the split. - On a maintenance pass, to find the highest-value fix: an orphan to link, a stub to expand, a missing concept the bundle keeps pointing at. - In CI, only if you opt in with `--fail-on warn`. Without that flag, lint exits `0` even with findings. ## How it works `lint` inspects exactly the things [section 9](/docs/spec/#9-conformance) forbids the validator from rejecting. It has its own report, never emits conformance errors, and stays advisory unless you gate it. Findings fall into six categories, each backed by individual checks: - **reachability**: orphans, concepts in no index, disconnected islands, degree-0 files (`orphan`, `not_in_index`, `disconnected_component`, `unlinked`); - **backlog**: demand-ranked missing concepts and broken index entries (`missing_concept`, `broken_index_entry`); - **completeness**: stubs and missing `title` / `description` / `timestamp` (`stub`, `missing_title`, `missing_description`, `missing_timestamp`); - **freshness**: concepts older than a cutoff (`stale`), computed only when you pass `--stale-after`; - **provenance**: uncited external claims and broken citations, per [section 8](/docs/spec/#8-citations) (`uncited_external`, `broken_citation`); - **hygiene**: duplicate titles, unused or undefined reference links, self-links (`duplicate_title`, `unused_reference_def`, `undefined_reference`, `self_link`). Two knobs tune specific checks: `--min-body N` sets the `stub` body threshold in characters (default 50), and `--stale-after DUR` sets the `stale` cutoff as a duration (`90d`, `12w`) or an ISO date (`2026-01-01`). `--only` and `--except` select checks by name. ## Try it ```bash okf lint docs/ ``` ```text OKF lint - docs concepts: 37 edges: 87 index.md: 10 log.md: 1 hubs: features/chat/sources/source-ingestion-pipeline (×12), … Backlog · info graph-view.md: referenced by 3 link(s) across 2 concept(s) but does not exist ! warn features/index.md: index links to missing concept `../../CHANGELOG.md` Completeness · info features/bundles/entry-editor.md: missing recommended field: description Hygiene ! warn link-suggestions.md: reference-style link `[:approved_ids]` has no matching definition (an invisible broken link) ⚠ 3 warn, 31 info ``` For a machine, `okf lint docs/ --json` emits `{ bundle, healthy, stats, findings }` as compact single-line JSON. That report is the substrate an agent consumes to reason about the two things lint deliberately does not compute, contradictions and semantic staleness, because both need an understanding of meaning. ## Pitfalls - **`--only` and `--except` take check names, not category names.** `okf lint docs/ --only orphan,stub` works; `--only reachability` is a usage error (exit `2`). The valid names are the parenthesized ones above. - **A plain `okf lint` never reports staleness.** The `stale` check runs only when you pass `--stale-after`; and the value must be a duration or an ISO date, a bare number like `90` is rejected. - **Findings do not fail the run.** Lint exits `0` with a screen full of warnings unless you pass `--fail-on warn`. If you want a hard gate, say so explicitly, in CI and in scripts. - **Do not send conformance questions here.** Lint never emits section 9 errors; a bundle can lint clean and still be non-conformant. Run [`okf validate`](/docs/cli/validate/) for legality. --- # okf loose URL: https://okfgem.com/docs/cli/loose/ Summary: List the concepts with no cross-links in or out, grouped by folder, so you can decide which files should join the graph and which are leaves by design. ## When to use it - After a burst of authoring, to see which new concepts never got wired into the graph. - On a curation pass, as the quick answer to "which files float?" without reading a full [`okf lint`](/docs/cli/lint/) report. - Before restructuring, to find the concepts nothing would miss if they moved. ## How it works `loose` lists every concept with graph degree 0: no cross-links in and none out. It is a focused, folder-organized view over lint's `unlinked` check; `okf loose ` reports the same set as `okf lint --only unlinked`, regrouped by directory so you can scan a directory at a time. A loose file is not automatically a defect. A terminal leaf (a backlog item, a spec reference) can be loose by design. The verb surfaces the set so you can judge intent: link the files that should participate in the graph, leave the deliberate leaves alone. It is advisory and always exits `0`. ## Try it ```bash okf loose docs/ ``` ```text Loose files - docs (3) decisions/ adr-0007-webhooks.md ADR 0007: webhooks over polling features/bundles/ entry-editor.md Entry editor entry-history.md Entry history ``` For a machine, `okf loose docs/ --json` emits `{ bundle, count, loose: [{ id, title, dir }] }` as compact single-line JSON. ## Pitfalls - **Loose is not orphan.** Lint's `orphan` check is about reachability, and an `index.md` listing makes a file reachable, so an indexed file is never an orphan. But an index listing is not a graph edge: a file can sit in an index and still have zero cross-links. `loose` catches exactly that gap. - **An empty report is not proof of curation.** Every concept having at least one link says nothing about whether the links are the right ones; run [`okf lint`](/docs/cli/lint/) for the full reachability picture. - **You cannot gate on it.** `loose` always exits `0`, on purpose, because a loose file can be intentional. Treat the output as a worklist to judge, not a failure list. --- # okf search URL: https://okfgem.com/docs/cli/search/ Summary: Ranked text retrieval across a bundle: case-insensitive terms, Ruby regexps, or an opt-in BM25+ full-text index over titles, ids, tags, types, descriptions, and bodies, with filters, projections, and context snippets. ## When to use it - The question is lexical: an exact symbol, an error code, a column name, a phrase. Structure will not surface those; text matching will. - After [`okf index`](/docs/cli/index/) told you where to look: scope the search with `--dir`, `--type`, or `--tag` and it stays surgical. - Instead of grep. Grep returns line noise from a tree of files; `search` returns ranked concepts with a snippet, and it knows what a title or a tag is. - When you do not know which bundle holds the answer. `okf search @all ` asks every bundle in the [registry](/docs/cli/registry/) at once. ## How it works `okf search ` matches every concept against all terms: each term must hit at least one searched field, though not necessarily the same one. Terms are case-insensitive substrings, or Ruby regular expressions with `--regexp` (short: `-e`). **It spans bundles.** The identity slot also takes several leading `@slug`s, or `@all` for every bundle in the [registry](/docs/cli/registry/): rankings merge across bundles with every row labeled by the bundle that answered (a `bundles` list and a per-match `slug` key in the JSON). Asking for everything tolerates gaps, so `@all` steps over a bundle whose directory has vanished with a note, while naming one insists on it. [One search across every bundle](/blog/okf-search-every-bundle/) walks a multi-bundle query end to end. ```bash okf search @all rate limit ``` ```text Search — @handbook @wiki · rate limit (2 of 40 concepts) @handbook runbooks/rate-limits Rate-limit runbook · Runbook · title @wiki onboarding Onboarding · Note · body …rate limits live in the billing docs… ``` **Matches rank by where they hit.** A title hit weighs 5, id 4, tags 3, type and description 2, body 1, summed over the fields that matched. Each row carries the concept's identity (id, title, type, dir, tags), the matched fields, the score, and one bounded context snippet from the strongest match that needs context. Three knobs keep it narrow: - `--in title,body` restricts which fields are searched (title, id, tags, type, description, body). - `--type`, `--dir`, `--tag` filter the candidate concepts first, the same filters every list view takes (the deprecated `--area` still works over the first path segment). - `--fields` / `--except` project the JSON rows down to the properties you will actually read. It is an advisory read: **exit `0` even with zero matches**. An unknown `--in` field or an invalid pattern is a usage error, exit `2`. ### Two engines, and why the default is the one it is Since 1.9.0 the matching itself is pluggable, and two engines ship: | | `scan` (default) | `index` | |---|---|---| | matches | raw text, literally | whole tokens and the tokens they prefix | | ranks by | summed field weight | BM25+ | | finds `ustomer` inside `customer` | yes | no | | tolerates typos | no | with `--fuzzy` | `--engine index` routes to [minifts](https://github.com/serradura/minifts), the pure-Ruby port of the same MiniSearch build the graph page loads. It buys exactly three things: **BM25+ relevance ranking**, **`--fuzzy`** (typo tolerance at edit distance `0.2 x term length`), and **parity with the graph page**, which runs the same engine, so the two rank identically. **The index is opt-in, and the benchmark is why.** A one-shot CLI builds an index, asks one question, and exits: end to end that is **3.00 s against 0.24 s at 1,000 concepts**, with the build accounting for around 95% of it. The per-query throughput that recommends an index (roughly 44 to 56 times the scan's) is the right measure for a long-lived index, a page or a server, and the wrong one for a process that exits. `--fuzzy` implies `--engine index`, and that routing is silent: no note, no header change, no new JSON key. Naming an engine that cannot do what you also asked is a usage error that names one that can (`--engine index -e` answers *try --engine scan*), and an unknown name lists what is available. `--help` reads the engine registry, so an addon's engine shows up without the CLI knowing it exists. ## Try it ```bash okf search docs/ dedup key ``` ```text Search — docs · dedup key (2 of 37 concepts) decisions/dedup-key Invoice dedup key · Decision · title+id+tags+body We chose the (account_id, external_id) pair as the dedup key, so a retried… services/billing Billing service · Service · body …retries reuse the dedup key, so a replayed invoice upserts instead of doub… ``` Patterns and machine output compose the same way as everywhere else in the CLI: ```bash okf search docs/ 'err_[a-z]+_409' --regexp --json --fields id,snippet ``` ```json {"bundle":"docs","query":["err_[a-z]+_409"],"count":1,"matches":[{"id":"runbooks/rate-limits","snippet":"…the gateway answers ERR_DEDUP_409 when a replay hits an open invoice…"}]} ``` When a term is a guess rather than a quotation, `--fuzzy` is the one that answers: ```bash okf search @okf serch # 0 of 24 concepts okf search @okf serch --fuzzy # 13 of 24 concepts ``` The default is not being unhelpful there. It is reporting, correctly, that nothing in the bundle contains those letters in that order. ## Pitfalls - **The default is exact on purpose.** No stemming, no approximation: the agent reading the results is the fuzzy layer, and `--fuzzy` is there for the times you would rather the tool were. When terms miss, learn the bundle's vocabulary from [`okf tags`](/docs/cli/tags/) and [`okf types`](/docs/cli/types/), then search again in the bundle's own words. - **Know what the index costs before you name it.** Its tokenizer splits on punctuation, so `customer_id` indexes as `customer` plus `id` and `7.2.0` as `7`, `2`, `0`. An infix finds nothing (`ustomer` matches `customer` under the scan and not under the index), and a backtick is Unicode `Sk` rather than punctuation, so a word inside a code span indexes with its backticks attached. Ranking does not rescue this: BM25+ normalizes by field length, so a short concept dense in `7`, `2` and `0` can outrank the one that actually says `7.2.0`. The default has none of these problems, because raw-text matching has no tokenizer. - **Cross-bundle scores mean different things per engine.** BM25+ prices a term by how rare it is, so `--engine index` indexes the searched bundles as one corpus: a score is relative to the whole answer, and the same concept scores lower searched beside other bundles than alone. The default's scores are absolute and need no such treatment. - **Map first on an unfamiliar bundle.** Search cuts across structure, but only [`okf index`](/docs/cli/index/) shows what a directory claims to hold and what is missing from it. The [skill's search playbook](/docs/skill/search/) sequences the two. - **Zero matches is an answer, not an error.** The exit code stays `0`; an empty result means the bundle does not carry those words, which is itself a curation signal worth writing back. --- # okf dirs URL: https://okfgem.com/docs/cli/dirs/ Summary: List a bundle's directories with the number of concepts living directly in each, root first and the total last. The small, shape-first read that stays legible on a bundle of any size, with --dir for subtree counts and --depth to walk the tree one level at a time. ## When to use it - As the **first read on a bundle you do not know**. One row per directory stays small whether the bundle holds thirty concepts or three thousand, so it orients you before you open anything. - To find **where the mass sits** before a consume or a [refine](/docs/skill/refine/) pass: `--dir` adds a subtree count, so you can see which branch carries the weight. - To **plan a descent**. Name the branch here, then open it with [`okf index --dir`](/docs/cli/index/). The skill leads with `okf dirs` for exactly this reason: `dirs` emits one row per directory where `index` emits one row per concept, so the two scale with different things. ## How it works `okf dirs ` lists every directory in the bundle with the number of concepts living **directly** in it, root first and the total last. The count is direct, never a rollup, so a directory holding nothing but sub-directories reads `0` rather than a hidden sum, and the column adds up to the bundle's concept count exactly. Every directory the tree has appears, including the empty intermediates that exist only to connect one branch to another, because each is still a directory you can address. JSON shape: `{ bundle, total, count, dirs: [{ dir, count, subdirs }] }`. Three flags shape the view: - **`--dir PATH`** (repeatable) narrows to one directory and everything below it, and adds a `subtree` count per row: the concepts at or below that directory. The subtree number is defined as exactly what `--dir PATH` itself returns, so the count and the flag can never disagree. A concept matches when its directory *is* the path or sits below it, so `--dir platform` reaches `platform/services/api`. `root` is the unquoted spelling of `.`, the bundle root alone; since 1.13.0 a bundle that actually has a `root/` directory keeps it addressable, because the real directory wins over the alias. Matching folds case, and a trailing slash on the label the views print is accepted. - **`--depth N`** keeps only that many directory levels below the starting point, where the starting point is the `--dir` when one is given and the bundle root otherwise. It is relative, not absolute, so `--dir a/b --depth 1` reads "a/b and one level under it" with no need to know how deep `a/b` already is. `--depth 0` is the starting point alone. Anything but a whole number is a usage error (exit `2`). - **`--fields`** / **`--except`** project the JSON down to the properties you want, over the row shape above. With `--dir`, the chain of ancestors up to the root is shown by default so the branch is never adrift of the context that names it; `--no-ancestors` drops it. Ascent and descent are separate axes, so `--depth` never bounds that chain. ## Try it ```bash okf dirs docs/ ``` ```text Dirs — docs Dir Concepts (root) 3 cli 15 skill 9 guides 3 4 dirs · 30 concepts ``` Find where the weight sits, with subtree counts: ```bash okf dirs docs/ --dir cli --depth 1 ``` The `subtree` column then reads the concepts at or below each row, and the deprecated first-segment rollup never enters it. To walk a large tree a level at a time, raise `--depth` one step at a time; to project the JSON, add `--fields dir,count,subtree`. ## Pitfalls - **The count is direct, not a subtree.** A parent directory's row counts only the concepts directly in it, which is why the column sums to the total. When you want "how much is at or below here", reach for `--dir` and read the `subtree` count. - **Empty intermediate directories read `0`, not nothing.** A directory that holds only sub-directories still appears, because it is still one `--dir` can address. That is information, not noise. - **`--depth` is relative to the starting point.** `--dir a/b --depth 1` is "a/b and one level under it", not "the second level of the whole tree". Ascent to the root is a separate axis and is not bounded by `--depth`. - **`dirs` speaks the full path.** The row `platform/services` is the whole directory, not a first segment. The deprecated `--area` / `--by area` only ever saw the first segment; the full-path world is `--dir` and [`stats`](/docs/cli/stats/)'s `by_dir`. --- # okf index URL: https://okfgem.com/docs/cli/index/ Summary: Print the progressive-disclosure map from spec section 6: every directory with its index body, type and tag rollups, child pointers, and concept listing. ## When to use it - First, when picking up an existing bundle. It is the cheapest high-signal orientation: the map, the rollups, and the listings in one pass. - To catch enumeration drift. An `index.md` that stopped listing a concept is invisible to grep (you cannot grep for an entry that is missing); the map makes the gap visible. - Before an agent reads anything else. `okf index --except body,listing` is the lean skeleton of the whole bundle in a few hundred bytes. ## How it works `index` implements the spec's [section 6 index files](/docs/spec/#6-index-files) as a view. It prints one entry per directory that holds concepts or carries an `index.md`, root first: the authored index body (frontmatter stripped), a `type` and `tag` rollup over the concepts that live directly there, its child directories, and the concept listing. It is also the one read verb that sees the reserved layer: `index.md` files are structural, so [`okf catalog`](/docs/cli/catalog/), [`okf files`](/docs/cli/files/), and the other concept views never show them. For a directory that has concepts but no `index.md`, the listing is synthesized from the concepts' descriptions and tagged `(no index.md)`; section 6 explicitly permits synthesizing a map on the fly. `--dir PATH` narrows the map to one directory and everything below it, and is repeatable (`--dir model --dir format` shows both): a concept matches when its directory *is* the path or sits below it, `root` (or `.`) names the bundle root (a real `root/` directory, where one exists, owns the name instead since 1.13.0), and matching folds case. `--depth N` keeps only that many directory levels below the starting point, which is the `--dir` when one is given and the bundle root otherwise. It is relative, not absolute, so `--dir a/b --depth 1` reads "a/b and one level under it"; `--depth 0` is the starting point alone, and anything but a whole number is a usage error (exit `2`). With `--dir`, the chain of ancestors up to the root is shown by default so the branch is placed in the context that names it, the root `index.md`'s prose first among them; those rows carry a leading `↑` and `ancestor: true`, stay out of `total`, and `--no-ancestors` drops them. Ascent and descent are separate axes, so `--depth` never bounds the chain. `--no-body` drops the prose to a skeleton of headers, rollups, and child pointers. It is a read view: advisory, always exit `0`. The `--area` flag is deprecated. It still works, but it warns, it maps to `--dir`, and it saw only the first path segment where `--dir` speaks the whole path. It cannot combine with `--depth` or `--dir`, and that pairing is refused (exit `2`). For the shape read these two flags share, one row per directory rather than a full map, see [`okf dirs`](/docs/cli/dirs/). ## Try it ```bash okf index docs/ --dir features ``` ```text Index map - docs (1 directory) features/ · 12 concepts · Feature 12 → bundles/ chat/ One concept per shipped capability. Start with the chat pipeline, then follow each feature's links into the models it reads. ``` To walk a large tree a level at a time, pair `--dir` with `--depth`: ```bash okf index docs/ --dir cli --depth 1 --except body,listing ``` For a machine, `okf index docs/ --json` emits `{ bundle, count, directories: [{ dir, index_path, present, synthesized, count, types, tags, subdirs, body, listing: [{ id, title, description, type, tags }] }] }` as compact single-line JSON. `--fields` / `--except` project that shape down (`--no-body` is shorthand for dropping just `body`), and on a large bundle dropping `body` and `listing` is the difference between a few hundred bytes and hundreds of KB: on one 414-concept bundle, `index --json` went from 313 KB to 2.8 KB at `--depth 1 --except body,listing`, which is what makes `index` usable at scale. ## Pitfalls - **`--dir` is repeatable, not comma-separated.** Pass the flag once per directory: `--dir model --dir format`. Matching folds case and reaches every directory below the one you name, and `root` names the bundle root so you never shell-quote `(root)`; a bundle with a real `root/` directory keeps it addressable, since the real directory wins over the alias. The deprecated `--area` saw only the first segment, so prefer `--dir`. - **A synthesized directory is a signal, never a defect.** `(no index.md)` means a map worth writing, but `index` emits no lint findings and never fails a bundle; the curation question belongs to [`okf lint`](/docs/cli/lint/). - **A `--dir` that matches nothing is an empty map, not an error.** The exit code stays `0`; check the `count` before assuming the directory exists. The ancestor chain is not printed for a `--dir` that matched nothing, so a lone root row never reads as a partial answer to a query that in fact matched nothing. - **Do not look for `index.md` in the other views.** Reserved files appear only here; the [bundle anatomy](/docs/bundle-anatomy/) page explains what makes them reserved. --- # okf catalog URL: https://okfgem.com/docs/cli/catalog/ Summary: List every concept with its full metadata (type, status, tags, timestamp, link degree, description), grouped by directory and filterable by type, directory, or tag. ## When to use it - To enumerate a bundle with full metadata: the "what's here, in detail" view, one line of context per concept. - To answer filtered questions without a browser: `okf catalog docs/ --tag auth` is "what carries the auth tag?", answered on the CLI. - As an agent's substrate for choosing what to read next, after [`okf index`](/docs/cli/index/) has provided the map and [`okf stats`](/docs/cli/stats/) the size. ## How it works `catalog` prints every concept with its metadata (type, status, tags, timestamp, in and out link degree, description), grouped by top-level directory. It reproduces the browser server's Catalog panel on the CLI, sharing one data source with [`okf files`](/docs/cli/files/), [`okf tags`](/docs/cli/tags/), [`okf types`](/docs/cli/types/), and [`okf stats`](/docs/cli/stats/): per-concept metadata plus link degree. It is an advisory read and always exits `0`. The view narrows with the same filters the browser offers: `--type TYPE`, `--dir PATH`, `--tag TAG`, combinable. Matching is case-insensitive and exact, and `--dir` reaches every directory below the one you name. A concept at the bundle root lives in the `(root)` directory, which `--dir` also accepts as plain `root`, no shell quoting needed; since 1.13.0 a real directory named `root/` owns that name instead, so it stays reachable. The deprecated `--area` still works over the first path segment. ## Try it ```bash okf catalog docs/ --tag chat ``` ```text Catalog - docs (3 of 37 concepts) features/ (3) Chat threads · Feature · ↳9 How a conversation thread is stored, trimmed, and replayed. Source ingestion pipeline · Feature · ↳12 Turns an uploaded source into chat-ready chunks. Streaming responses · Feature · ↳4 Why responses stream token by token and what the client contract is. ``` For a machine, `okf catalog docs/ --json` emits `{ bundle, count, concepts: [{ id, title, type, description, tags, timestamp, status, backlog_ref, dir, top_dir, links_out, links_in }] }` as compact single-line JSON (`dir` is the full path, `top_dir` the first-segment rollup renamed from `area` in 1.12.0). `--fields` / `--except` project each concept down to the properties you will read, e.g. `--fields id,title,links_in` for a ranked reading list. ## Pitfalls - **Filters are case-insensitive but exact.** `--type feature` matches `Feature`; `--type feat` matches nothing. There is no substring or glob matching. - **A filter that matches nothing is an empty view, not an error.** The exit code stays `0` and `count` is `0`; do not read an empty catalog as a broken bundle. - **`--fields` and `--except` are mutually exclusive**, and an unknown field name is a usage error (exit `2`) that lists the valid ones. Both imply `--json`. - **Reserved files never appear.** `index.md` and `log.md` are structure, not concepts; see [`okf index`](/docs/cli/index/) for that layer. --- # okf files URL: https://okfgem.com/docs/cli/files/ Summary: Print the bundle's folder tree, each concept's filename and title grouped by directory, the fastest way to see how the knowledge is laid out on disk. ## When to use it - To see a bundle the way the filesystem does: filenames and titles, folder by folder. - To map a concept id to its path (or the reverse) before editing, since a concept's path is its id. - To answer "where do concepts of this kind live?" with a filter: `okf files docs/ --type Guide` lists just the guides, in place. ## How it works `files` prints each concept's filename and title, grouped by directory. It reproduces the browser server's Files panel on the CLI, sharing its data source with [`okf catalog`](/docs/cli/catalog/) and the other read views: it is the "how it's organized" cut of the same per-concept metadata. Advisory read, always exit `0`. The view narrows with `--type TYPE`, `--dir PATH`, and `--tag TAG`, combinable. Matching is case-insensitive and exact, `--dir` reaches every directory below the one you name, and it accepts `root` for the bundle root (a real `root/` directory, where one exists, owns the name instead since 1.13.0). The deprecated `--area` still works over the first path segment. ## Try it ```bash okf files docs/ --type Guide ``` ```text Files - docs (4 of 37 files) guides/ getting-started.md Getting started importing-sources.md Importing sources exporting.md Exporting a bundle troubleshooting.md Troubleshooting ``` For a machine, `okf files docs/ --json` emits `{ bundle, count, files: [{ path, id, dir, type, title, description }] }` as compact single-line JSON. `--fields` / `--except` project each entry, e.g. `--fields path,title` when the paths are all you need. ## Pitfalls - **Reserved files are missing on purpose.** `files` lists concepts, and `index.md` / `log.md` are structure, not concepts. If the tree looks shorter than `ls` says, that is why; [`okf index`](/docs/cli/index/) shows the reserved layer. - **Filters are case-insensitive but exact.** `--dir guide` will not match `guides`; a filter that matches nothing is an empty view with exit `0`, not an error. - **`--fields` and `--except` are mutually exclusive**, both imply `--json`, and an unknown field name is a usage error (exit `2`) that lists the valid ones. --- # okf tags URL: https://okfgem.com/docs/cli/tags/ Summary: List every tag with the concepts that carry it, ordered by count, and regroup by type or directory to see which themes connect the bundle and which scatter. ## When to use it - To find the thematic clusters in a bundle: the tags with the highest counts are the themes the bundle keeps returning to. - To curate the tag vocabulary. `--by type` or `--by dir` shows where each tag lives, which separates connective tags from scattered one-offs. - To answer scoped questions: `okf tags docs/ --dir billing --json` is "which tags does the billing directory use?". ## How it works `tags` prints every tag with the concepts that carry it, ordered by count descending. It reproduces the browser server's Tags panel on the CLI, over the same data source as [`okf catalog`](/docs/cli/catalog/) and the other read views. Advisory read, always exit `0`. `--by type` or `--by dir` regroups the list per concept dimension with within-group counts; a tag spanning several groups appears in each. `--by dir` cuts by the whole directory path (the deprecated `--by area` saw only the first segment). That regrouped view is the substrate for tag curation: a tag confined to one group at count 1 is scattered, one recurring across groups is connective. The [curation model](/docs/curation/) covers the judgment side. The view narrows with `--type TYPE` and `--dir PATH` (there is no `--tag` here; the tag dimension is the output). Matching is case-insensitive and exact, `--dir` reaches every directory below the one you name, and it accepts `root` for the bundle root (a real `root/` directory, where one exists, owns the name instead since 1.13.0). The deprecated `--area` still works over the first path segment. ## Try it ```bash okf tags docs/ ``` ```text Tags - docs (24 distinct) chat 6 Chat threads, Source ingestion pipeline, Streaming responses, Chat mo… sources 5 Source ingestion pipeline, Importing sources, Source dedupe, Source s… graph 5 Graph model, Graph layout, Link suggestions, Entry editor, Exporting … editor 4 Entry editor, Entry history, Link suggestions, Troubleshooting ``` For a machine, `okf tags docs/ --json` emits `{ bundle, count, tags: [{ tag, count, concepts: [id, …] }] }`, and `okf tags docs/ --by dir --json` emits `{ bundle, count, by, groups: [{ dir, count, tags: […] }] }` (the group key matches the dimension), both as compact single-line JSON. ## Pitfalls - **`tags` cannot filter by tag.** Each read view takes the filters orthogonal to itself; here that means `--type` and `--dir` only. To ask "what carries tag X?", flip the question to [`okf catalog`](/docs/cli/catalog/) `--tag X`. - **`--by` counts are within-group.** A tag carried by concepts in three directories appears three times, once per group, each with that group's count. Do not sum the groups and expect the flat view's totals. - **Filters are case-insensitive and exact**, and a filter that matches nothing is an empty view with exit `0`, not an error. --- # okf types URL: https://okfgem.com/docs/cli/types/ Summary: List every concept type with the concepts that carry it, ordered by count descending, the quickest read on what kinds of knowledge a bundle holds. ## When to use it - To learn a bundle's type vocabulary before adding to it, so a new concept reuses an existing `type` instead of inventing a near-duplicate. - To spot skew: one type with thirty concepts and four types with one concept each is a modelling smell worth a look. - To answer scoped questions: `okf types docs/ --dir decisions` shows which kinds of knowledge the decisions directory holds. ## How it works `types` prints every type with the concepts that carry it, ordered by count descending. It reproduces the browser server's Types panel on the CLI, over the same data source as [`okf catalog`](/docs/cli/catalog/) and the other read views. Since `type` is the one field [section 9](/docs/spec/#9-conformance) makes mandatory, this view covers every concept in a conformant bundle. Advisory read, always exit `0`. The view narrows with `--dir PATH` and `--tag TAG` (there is no `--type` here; the type dimension is the output). Matching is case-insensitive and exact, `--dir` reaches every directory below the one you name, and it accepts `root` for the bundle root (a real `root/` directory, where one exists, owns the name instead since 1.13.0). The deprecated `--area` still works over the first path segment. ## Try it ```bash okf types docs/ ``` ```text Types - docs (5 distinct) Feature 14 Chat threads, Source ingestion pipeline, Link suggestions, Entry ed… Model 8 Graph model, Source model, Thread model, Chunk model, Tag model, Us… Guide 6 Getting started, Importing sources, Exporting a bundle, Troublesho… Decision 5 ADR 0003: chunking strategy, ADR 0007: webhooks over polling, ADR 0… Concept 4 Overview, Glossary, Bundle lifecycle, Progressive disclosure ``` For a machine, `okf types docs/ --json` emits `{ bundle, count, types: [{ type, count, concepts: [id, …] }] }` as compact single-line JSON. ## Pitfalls - **`types` cannot filter by type.** Each read view takes the filters orthogonal to itself; here that means `--dir` and `--tag` only. To ask "which concepts are Decisions?", flip the question to [`okf catalog`](/docs/cli/catalog/) `--type Decision`. - **Filters are case-insensitive and exact.** `--tag graph` matches `Graph`; `--tag gra` matches nothing, and a filter that matches nothing is an empty view with exit `0`, not an error. - **A ragged type list is a curation finding, not a conformance one.** Near-duplicate types (`Guide` next to `Guides`) pass [`okf validate`](/docs/cli/validate/) fine; this view is where you notice them, and merging is on you. --- # okf stats URL: https://okfgem.com/docs/cli/stats/ Summary: Print bundle rollups (concept, directory, type, cross-link, and distinct-tag totals plus per-type and per-directory breakdowns) to size any bundle in one command. ## When to use it - First contact with an unknown bundle: is this 10 concepts or 400? The answer decides whether you read it whole or go through [`okf index`](/docs/cli/index/) progressively. - To watch a bundle's shape over time: cross-links growing faster than concepts means densification, the reverse means sprawl. - As the cheapest health snapshot before deciding whether a [`okf lint`](/docs/cli/lint/) pass is due. ## How it works `stats` prints the bundle rollups: concept, directory, concept-type, cross-link, and distinct-tag totals, plus per-type and per-directory breakdowns ordered by count. The human breakdown reads **By dir**, keyed by the whole directory path. It reproduces the browser server's Stats panel on the CLI, derived from the same data source as the other read views. It is an advisory read, always exit `0`, and takes no filters: the whole bundle, one screen. ## Try it ```bash okf stats docs/ ``` ```text Stats - docs concepts 37 dirs 6 concept types 5 cross-links 87 distinct tags 24 By type Feature 14 Model 8 Guide 6 Decision 5 Concept 4 By dir features 18 models 8 guides 4 decisions 3 ops 2 (root) 2 ``` For a machine, `okf stats docs/ --json` emits `{ bundle, concepts, top_dirs, dirs, concept_types, cross_links, distinct_tags, by_type, by_top_dir, by_dir }` as compact single-line JSON: `top_dirs` / `by_top_dir` are the first-segment rollup (the field renamed from `area` in 1.12.0), and `dirs` / `by_dir` are the full-path cut. ## Pitfalls - **There are no filters here.** `stats` sizes the whole bundle; for a per-directory or per-tag question, use [`okf catalog`](/docs/cli/catalog/), [`okf tags`](/docs/cli/tags/), or [`okf types`](/docs/cli/types/) with their filters. - **The counts cover concepts that parse.** A file with invalid frontmatter is skipped and noted on stderr, so the totals can undercount a broken bundle. Run [`okf validate`](/docs/cli/validate/) to find the files the numbers left out. - **Reserved files are not concepts.** `index.md` and `log.md` never appear in the `concepts` count; if the total looks low next to `ls | wc -l`, that gap is by design. --- # okf graph URL: https://okfgem.com/docs/cli/graph/ Summary: Print the knowledge graph as nodes and edges, with JSON dumps at three sizes (full, no-body, minimal) for piping into analysis or planning a traversal. ## When to use it - To feed the bundle's structure into your own analysis: centrality, clustering, dead-end detection, anything the built-in views do not compute. - To plan a traversal before consuming a large bundle: pull the minimal graph, pick the hubs, then read only the concepts on the path. - When you want the structure the interactive server visualizes, but as data. For the visual version, see [`okf server`](/docs/cli/server/). - To rank the hubs or weigh the directories without computing it yourself: `--hubs` ranks concepts by inbound links, `--traffic` reads cohesion against coupling one directory at a time. Both make the [refine](/docs/skill/refine/) playbook's structural judgements mechanical. ## How it works `graph` builds the node and edge graph from every concept that parses. Plain text prints just the totals; the substance is in `--json`, which emits a machine-readable dump: `nodes` with `id`, `type`, `title`, `description`, and `tags`, plus `edges`. Two flags trim the payload: `--no-body` drops each node's body, and `--minimal` ships only `id` and `title` per node plus the type and tag indexes, the lean shape the [server](/docs/cli/server/) page boots from. Like the server, `graph` is best-effort under [section 9](/docs/spec/#9-conformance): a file with invalid frontmatter is skipped and noted on stderr, never fatal, so one bad file cannot break the dump. The [graph server](/docs/graph-server/) page covers how the same graph drives the browser view. Two aggregate reads answer structural questions the raw dump would make you compute. **`--hubs`** ranks every concept that has at least one inbound link by inbound degree, and groups each hub's inbound links by the directory they come from (`core/status ×3 flows 2, billing 1`). A hub whose inbound majority is foreign to its own directory is a move candidate, which is the [refine](/docs/skill/refine/) playbook's origin test made mechanical. JSON: `{ bundle, count, hubs: [{ id, top_dir, inbound, by_top_dir }] }`. **`--traffic`** reads the graph one grain coarser, by directory rather than concept: it collapses each concept into its directory and the links between two directories into one weighted arc, then reports internal, out, and in traffic per directory with a **cohesion**, its internal share of that total. Rows sort by cohesion ascending, so the directories with a case to answer come first: near-zero cohesion under heavy inbound is a shared vocabulary doing its job, heavy outbound with nothing back is a projection wearing a directory, and a directory with no traffic prints a dash rather than a `0%` it did not earn. The arc **cut** is fitted to the bundle rather than fixed, and `--cut N` overrides it; cohesion is computed over every arc regardless, so narrowing the drawn picture never moves the evidence. JSON: `{ bundle, cut, fitted, dirs, arcs, total_arcs }`. ## Try it ```bash okf graph docs/ ``` ```text 37 concepts, 87 links ``` ```bash okf graph docs/ --minimal --json ``` emits `{ nodes: [{ id, title }], edges, types, tags }` as compact single-line JSON (the type and tag indexes ride along only with `--minimal`); the full `okf graph docs/ --json` emits `{ nodes: [{ id, type, title, description, tags, body }], edges }`. Add `--pretty` to indent either for reading. Weigh the directories to ask whether each is a concern or a container: ```bash okf graph docs/ --traffic ``` ```text Traffic - docs (4 dirs, 9 of 14 arcs at weight 2 or more) Dir Concepts Internal Out In Cohesion guides 3 0 8 2 0% cli 15 9 11 9 31% skill 9 7 6 8 35% (root) 3 2 1 3 40% ``` `guides` at `0%` cohesion with links only outward is a container, files grouped by what they are for; `skill` at `35%`, its concepts mostly citing one another, is a concern. Rank the hubs instead with `okf graph docs/ --hubs`. ## Pitfalls - **The plain-text view is only a summary.** Two numbers, nothing else; without `--json` there is no structure to pipe anywhere. - **The full dump carries every body.** On a large bundle that is by far the biggest share of the payload; reach for `--no-body` or `--minimal` unless you actually need the prose. - **Best-effort means silently smaller, loudly noted.** Skipped files shrink the graph and the note goes to stderr, so a pipeline reading stdout will not see it. Gate on [`okf validate`](/docs/cli/validate/) first if completeness matters. - **`--hubs` and `--traffic` are aggregates, not the graph.** They summarize the same edges the dump carries; when you need the raw nodes and edges, reach for `--json`. And `--traffic`'s `--cut` only narrows what is drawn, since cohesion is computed over every arc, so a tighter cut never changes the numbers. --- # okf registry URL: https://okfgem.com/docs/cli/registry/ Summary: A persistent, per-user list of bundles: register a directory once, name it @slug from anywhere, and a bare okf server hosts the whole list behind one hub. ## When to use it - You work across more than one bundle. Register each once and every verb reaches it by name: `okf lint @handbook` works from any directory, no path required. [Many bundles, one registry](/blog/okf-registry-many-bundles/) walks the setup end to end. - You want the names to travel with the repo. `okf registry init` drops a project-local `.okf-registry.json` you commit, so a teammate who clones gets the same `@slug`s and a bare `okf server` works with no global setup on the machine. - You want one name for a set of bundles. `okf registry group backend @orders @billing @shared` gives the set a single slug you search and serve as a unit. - You want one graph page over all of them. A bare `okf server` hosts every registered bundle behind one hub, and `Cmd/Ctrl-K` switches between them; [`okf server`](/docs/cli/server/) has the details. - You ask cross-bundle questions. `okf search @all ` ranks matches across every registered bundle in one query, each row labeled with the bundle that answered; [`okf search`](/docs/cli/search/) covers it. ## How it works `okf registry` manages a plain JSON file at `$OKF_HOME/registry.json`, with `$OKF_HOME` defaulting to `~/.okf`. It stores references, never content: a path, a slug, and a title per bundle. The bundles stay where they are on disk, owned by the repos they document, so nothing is copied and nothing can go stale except the path itself. The file is meant to be read, grepped, and hand-edited. ```bash okf registry init # create a project-local .okf-registry.json here okf registry list # every entry; * marks the default okf registry set [--as SLUG] # add or update (identity is the path) okf registry del # remove the entry (or a group); the bundle stays on disk okf registry default @slug # move an entry to the front okf registry rename # change the name, keep the position okf registry group <@member…> # name a set of bundles or groups; nests okf registry ungroup <@member…> # remove members; emptying deletes the group ``` **The slug is the bundle's name everywhere.** It is minted from the directory basename unless you choose one with `--as`. The two paths differ on collision, deliberately: a minted name that is taken gets a suffix (`docs` becomes `docs-2`), because you never asked for it, while a name you chose with `--as` or `rename` is refused instead, because silently serving a different one would be a lie. `all` is reserved on every path in, since `@all` already means every registered bundle. **The list is ordered, and the first entry still on disk is the default**: the bundle a bare `okf server` opens at `/`, and the one a bare `@` names. `registry default @slug` moves an entry to the front, and `registry set --default` registers straight to it. Nothing else is stored, so nothing else can drift: a rename keeps its position, a `del` promotes whatever is next, and the file cannot name a default that is not in it. ### A registry can live in the repo `okf registry init` creates a project-local `.okf-registry.json` in the current directory. Once it exists, okf discovers it by walking up from wherever you are, and every registry operation and every `@slug` resolves through it instead of the global `$OKF_HOME` one, so a bare `okf server` inside a repo serves that repo's bundles with no global setup on the machine. The nearest registry on the path wins, since nested ones resolve nearest-first; `okf registry list` names the local file it found, and `OKF_NO_DISCOVERY=1` forces the global one, the escape hatch for a fixed-directory caller such as CI. A project-local registry stores **portable paths**. A bundle inside the registry's own tree is written relative to the `.okf-registry.json`, so the file can be committed and a checkout on another machine, or a container that mounts it, resolves the same bundles unchanged. A bundle outside the tree keeps an absolute path, because it cannot travel. Paths still read back absolute everywhere the CLI reports them, the relative form lives only on disk, and an existing absolute local entry migrates to relative on its next write. The global `$OKF_HOME` registry is unchanged and stores absolute paths as before. ### Groups: one slug for a set of bundles A group is a registry slug that names a list of members, and a member can be a bundle **or another group**, so groups nest; it resolves recursively and path-deduped down to its bundle leaves. `okf registry group <@member…>` creates one or adds to it, `ungroup` removes members (and emptying a group deletes it), and `del` and `rename` reach a group slug too, so one rename cascades across every member list and one `del` drops the slug and deletes any group it empties. `okf search @backend` merges the members into one ranking and `okf server @backend` mounts each of them, the first at `/`, both skipping a vanished member with a note exactly as `@all` does. **Every single-bundle verb refuses a group and exits `2`**, the same rule that refuses a second bundle. ### @slug, wherever a directory goes Registering gives a bundle a name the whole CLI understands. Wherever a verb takes a ``, `@slug` names a registered bundle and a bare `@` names the default: ```bash okf lint @handbook # from anywhere, no path okf render @ -o graph.html # the default bundle, exported okf search @all rate limit # every registered bundle, ranked together okf server @handbook @wiki # a hub of exactly these two ``` The registry-editing verbs (`del`, `default`, `rename`) take the slug bare or with the `@`; the reading verbs need the `@`, since a bare word there is a path. ## Try it ```bash okf registry set ./docs --as handbook ``` ```text registered handbook → /Users/you/work/billing/docs (37 concepts) ``` ```bash okf registry list ``` ```text * handbook billing/docs (/Users/you/work/billing/docs) wiki team/wiki (/Users/you/work/team/wiki) ``` From here `okf server` with no arguments serves both behind one hub, and any verb takes `@handbook` in place of the path. ## Pitfalls - **A running server does not follow the file.** The hub reads its bundles at boot, so registering, renaming, or deleting one needs a server restart to show. - **A discovered registry outranks `$OKF_HOME`.** When a `.okf-registry.json` sits at or above your working directory, it wins and every verb resolves through it; `$OKF_HOME` names the global registry, used only when no local one is found or when `OKF_NO_DISCOVERY=1` forces it. `$OKF_HOME` still names exactly one registry, with no fallback to `~/.okf` behind it, and an empty value counts as unset. - **A single-bundle verb refuses a group.** `okf lint @backend` exits `2` when `@backend` is a group, the same as pointing it at two bundles; `search` and `server` are the verbs that take a set. - **Committing a local registry rewrites its own paths once.** An existing absolute local entry migrates to relative on its next write, so a `.okf-registry.json` you already had can rewrite its paths the first time you touch it. In-tree bundles go relative so they travel; out-of-tree bundles stay absolute. - **Deleting from the registry deletes nothing on disk.** `del` removes the reference; the bundle stays where it always was. The reverse also holds: deleting a directory does not prune its entry. `registry list` marks it `missing` and leaves the decision to you, and the default quietly skips it, since the star must name a bundle `/` can actually open. - **A path argument names a location, only a location.** `registry del ./notes` matches an entry by that path and never falls through to the slug `notes`, so you cannot remove a bundle that merely shares a name with a local directory. --- # okf server URL: https://okfgem.com/docs/cli/server/ Summary: Serve the bundle as an interactive graph over HTTP: nodes colored by type, sanitized live markdown panels, filters, and search, from one local command. ## When to use it - To explore a bundle the way it is meant to be read: as a graph, following links instead of directory listings. - While curating: edits show on the next click without a restart, so you can fix a concept and immediately see it re-rendered. - To show a bundle to someone. The [live demo](https://demo.okfgem.com) is this server running on the gem's own bundle. - To keep every bundle you work with one command away: a bare `okf server` hosts the whole [registry](/docs/cli/registry/) behind one hub. ## How it works `okf server` takes zero or more bundles, and the count picks the mode. One directory (or one `@slug`) is the classic single bundle at `/`. Two or more mount behind a hub, each at `/b//`, with the first at `/`. A `@group` from the [registry](/docs/cli/registry/) mounts each of its members the same way, the first at `/`. None at all serves every bundle in the registry, its default at `/`. Behind a hub, `/b/` is a browsable index of the hosted bundles, an unknown slug answers with a page listing the way home instead of bare text, and `Cmd/Ctrl-K` opens a switcher to move between bundles without leaving the page. It starts a local HTTP server and prints its URL; stop it with Ctrl-C. `-p`/`--port` picks the port (default `8808`), `--bind` the address (default `127.0.0.1`), and `--title`, `--link`, and `--layout` tune the header and the initial layout. `--map` opens the graph with no links and the directories boxed, the far end of the link-amount control. `--read-only` declines the registry management the graph page offers by default (the Bundles panel and its write routes), which is on for a loopback bind and refused outright on any other address. Responses are gzipped whenever the client accepts it, which browsers do. The compression sits at the boot seam, `Rack::Deflater` wrapped around the app as the CLI starts it: lossless, transparent, and at no new dependency, because Deflater ships inside the `rack` the gem already requires. A client that sends no `Accept-Encoding` keeps getting identity responses. The wrap is boot policy rather than part of the app, so a host mounting `OKF::Server::App` in Rails brings its own compression, and the static file [`okf render`](/docs/cli/render/) writes is served however your host serves it. The page boots from a lean payload (nodes carry only `id` and `title`, plus compact type and tag indexes) and fetches each concept's markdown body live from disk as you click it, so the initial load stays small and edits show without a restart. Concepts render as nodes colored by `type` and sized by degree, links as edges, with a detail panel (rendered markdown, "Links to" and "Linked from" backlinks), layout switching, type/directory/tag filters on every view, and search. Search is a ranked MiniSearch index shared by the graph, catalog, and files views: several terms are ANDed, a term matches the tokens it prefixes as you type, and a typo still lands. Under the live server that index stays metadata-only (ids, titles, types, tags, descriptions), because bodies are fetched lazily and are not in the page to index; [`okf render`](/docs/cli/render/) bakes them in and searches them too. On the server side the search corpus is built once and warmed at boot rather than rebuilt per request, so a query answers in milliseconds; a hub drops it on any registry write so a held index never outlives its set. The graph page also carries a **Bundles panel** for managing the registry from the browser, which [`--read-only`](/docs/graph-server/) declines; the [graph server](/docs/graph-server/) page has the link-amount control, the spine, and that panel in full. Mermaid code blocks in a body render as diagrams, and a click or tap opens the diagram fullscreen with drag to pan and wheel or pinch to zoom. It is a Rack app, so the same server can be mounted in a host app such as Rails; the [library page](/docs/library/) shows how. The page is one template from a phone to a desktop, and it is keyboard-first. On small screens the navigation rail becomes a drawer, the toolbar folds into a settings sheet, and the panels go full-bleed; rotate a tablet and the layout re-evaluates rather than staying stuck. `Cmd/Ctrl-K` opens a command palette in every mode, views always in the list and bundles too when a hub is serving them; `/` jumps to the current view's search; `?` answers with a sheet of every shortcut, also reachable from the rail; `Esc` clears the graph selection. On the trust side, the page defends the two paths a hostile bundle could use: inlined graph data is escaped so it cannot break out of its ``-escaped exactly like the boot payload and every body still renders through `DOMPurify.sanitize(marked.parse(...))`, so the trust boundary holds; the trade-off is weight — each body is inlined, so a big bundle makes a big file, and `okf server` stays the choice at scale. - Official Docker image: `ghcr.io/serradura/okf`, a portable CLI that runs every `okf` command (the graph server included) with no Ruby on the host. It is built from source and published multi-arch (`linux/amd64`, `linux/arm64`) to the GitHub Container Registry on each release tag, so the image always matches the gem. Mount a bundle at `/data`; for `server`, add `--bind 0.0.0.0` and publish `-p 8808:8808`. See the README's Docker section. ## [1.5.0] - 2026-07-13 ### Added - New CLI verb: `okf search ` — deterministic ranked retrieval over concept metadata *and bodies*, the browser page's search brought to the CLI. Terms AND together as case-insensitive substrings, or as Ruby regexps with `--regexp`/`-e`; `--in` restricts the searched fields; the shared `--type/--area/--tag` filters and `--fields/--except` projections apply. Matches rank by where they hit (title > id > tags > type/description > body) and carry a bounded context snippet, so "which concept covers X?" costs a few rows instead of a body read. Advisory read: exit 0 even with no matches. Deliberately not fuzzy — the consuming agent is the fuzzy layer. - The skill learns retrieval as a first-class verb: a new `search` playbook (progressive disclosure end to end: ingest `okf index`, decide where to look, cut across with `okf search`, read only the winning bodies), search-aware routing in SKILL.md and the menu/consume playbooks, and `/okf:gem search ` first in the Claude Code plugin's routing. - Retrieval eval in the suite: the progressive path (index skeleton → search → one body) must answer a planted question in under 25% of the bytes of the full graph dump, so the playbook's economics stay true by construction. - Graph server: the authored layer joins the UI. The Files view carries two tabs — **Files** (the per-directory concept groups, foldable) and **Indexes** (the log first, as the chronological index, then every `index.md`, root before nested) — with the files filters moved up into the top bar. The rail's **Index** item, the `2` key, and `?view=index` are shortcuts straight to the Indexes tab. Folder nodes in file-tree mode and area boxes in cluster mode are clickable and open that directory's §6 map in the inspector (authored, or the synthesized listing when none exists). Links to an `index.md`, a `log.md`, or a bare directory (`model/`) navigate everywhere a body renders instead of striking through as dead, and the log is fetched fresh on every read, so a just-appended entry shows without a restart. A reserved file's "Open in graph" jumps to its folder in the file tree, map in the inspector. New `/index` and `/log` endpoints back it all. - Graph server: Mermaid diagrams in concept bodies are click-to-inspect. A click (or tap) opens the diagram full screen — drag to pan, wheel or pinch to zoom, buttons and double-click reset, Esc closes — powered by [Panzoom](https://github.com/timmywil/panzoom), lazy-loaded from the CDN exactly like Mermaid itself. ### Changed - The Claude Code plugin's `/okf:gem` command now weighs the shape of a free-form ask: a question about what the bundle knows routes through the search playbook and answers from retrieved concepts instead of guessing. - Skill efficiency audit: every playbook now takes the CLI's lean paths. `maintain` hunts affected concepts with `okf search` and pulls edges via `graph --json --minimal` instead of the full-body dump, `menu` reads the plain-text reports it only scans, and SKILL.md pins the discipline as a rule: skeleton first, bodies last. ### Fixed - Docs: the CLI reference's server section now reflects the DOMPurify sanitization that landed in 1.1.0 (it still said bodies render unsanitized), and the server page's link-preview image points at the renamed `okfgem.com/og-demo-v2.png`. ## [1.4.0] - 2026-07-12 ### Changed - Graph server UX round. Selecting a node now makes one camera move instead of two (the pan used to race the opening panel and the debounced canvas resize, a dizzying double movement; rapid clicks also queued animations — both fixed). Relative markdown links inside the inspector and the files preview resolve against the open concept and navigate in-app — clicking `../model/graph.md` selects that concept instead of 404ing the page; external links open in a new tab; links that leave the bundle are disabled, never a 404. Nodes are smaller (14–44px, was 24–70) and layouts keep a real gap between them (`nodeOverlap` for cose, `avoidOverlap`/`spacingFactor` elsewhere). The inspector and the files list are drag-resizable (persisted, double-click resets), and the files reader now uses the full pane width. New file-tree mode on the graph toolbar: folders become nodes and the only edges are folder→child, an acyclic layered tree of the bundle's files. On small screens (≤900px) the inspector starts hidden and opens on the first node tap; camera moves are gentler (450ms, ease-in-out). ## [1.3.0] - 2026-07-12 ### Added - The graph server page now emits link-preview metadata: Open Graph and Twitter Card tags with a social image, plus `theme-color` and `color-scheme`, so a shared `okf server` URL unfurls as a proper card in chat and social apps. - Docs: a themed README hero (light and dark), a GitHub social preview image, and Website / Live demo / Claude Code plugin links. ## [1.2.0] - 2026-07-12 ### Added - Claude Code plugin. The repository now doubles as a plugin marketplace: `/plugin marketplace add serradura/okf-gem`, then `/plugin install okf@okfgem`. The plugin carries the canonical skill (a generated copy; `rake plugin:sync` keeps it in lockstep with `lib/okf/skill`, and a test fails on drift), one front-door command (`/okf:gem`: no arguments orients on the CLI, the bundle, and what `validate`/`lint` report and recommends the highest-value next move without running one, `doctor` installs the gem and doctors the repo's bundle, `curate` runs the full validate + lint + loose cycle, anything else hands the task to the skill), and a PostToolUse hook that runs `okf validate` + `okf lint` after every edit inside a bundle and hands the relevant findings back as context: every conformance error, plus the warnings and lint findings that concern the edited file. The checks are the CLI's own, so the feedback is deterministic. The hook stays silent outside bundles, and when the CLI is missing it suggests `/okf:gem` once per session instead of erroring on each edit. It is config-free to silence: `OKF_CURATE_DISABLED=1` turns it off, `OKF_CURATE_QUIET=1` keeps the findings but drops that suggestion, and an `` comment in a file skips curation for that one. The skill routes through per-verb playbooks (`playbooks/`), and its signature guidance lines carry stable `` / `` markers. Nothing under `plugin/` ships in the gem. ## [1.1.0] - 2026-07-12 ### Changed - `require "okf"` now loads the pure library only. The two argv-facing shells — `OKF::CLI` and the `OKF::Skill` installer — load on demand, from `exe/okf` or an explicit `require "okf/cli"` / `require "okf/skill"`. `optparse` moves with the CLI, so an embedding app (e.g. a Rails store) that only reaches for the in-memory model and on-disk handles no longer pulls in the command-line machinery. The CLI itself is unchanged. ### Security - The graph server now sanitizes every concept body before rendering it. The page runs marked's HTML output through [DOMPurify](https://github.com/cure53/DOMPurify) (loaded from the same CDN as Cytoscape and marked) on the way to the DOM, so a bundle carrying active content in a Markdown body can no longer script the viewer. Inlined graph data was already escaped through `json_for_script`; this closes the other path. ## [1.0.0] - 2026-07-12 Initial release. ### Added - `OKF::Concept` / `OKF::Bundle`: pure in-memory model of an OKF v0.1 bundle, buildable straight from data (no disk) with link, citation, and markdown round-trip primitives. - `OKF::Bundle::Validator`: the spec §9 conformance gate (hard errors) with the spec's soft guidance reported as warnings — broken cross-links are tolerated, as §5.3 requires. - `OKF::Bundle::Linter`: advisory curation-quality report across reachability, backlog, completeness, freshness, provenance, and hygiene, with `--json` as a machine substrate. - `OKF::Bundle::Graph`: the knowledge graph (nodes, edges, type/tag indexes) at selectable fidelity. - On-disk handles: `OKF::Bundle::Folder`, `OKF::Bundle::Reader`, `OKF::Bundle::Writer` (atomic, validate-before-publish), and `OKF::Concept::File`. - `OKF::Server::App`: the interactive graph as a mountable Rack app — five views (graph, catalog, files, tags, stats) with type/area/tag filtering throughout, bodies fetched live from disk — served by a built-in WEBrick runner (`okf server`). - `okf` CLI: `validate`, `lint`, `loose`, and `graph`, plus the read views as text — `index`, `catalog`, `files`, `tags`, `types`, `stats` — at full parity with the browser: every list view narrows with `--type`/`--area`/`--tag` (case-insensitive; the bundle root is area `(root)`, accepted as `root`), and `tags --by type|area` regroups the tag index per concept dimension with within-group counts — the tag-curation view. `server` boots the graph page; `skill` installs the companion skill. - `okf index`: a read view over the progressive-disclosure layer (spec §6) — one entry per directory that holds concepts or carries an `index.md`, root first, with its authored index body (frontmatter stripped), a type/tag rollup over the concepts that live there, its child directories, and the concept listing. A directory with concepts but no `index.md` has its listing synthesized (§6 permits it) and is flagged. `--area` (repeatable), `--no-body`, and `--json`; advisory, always exit 0. Backed by the pure `OKF::Bundle#directory_index`. - JSON output is **compact by default** across every emitting verb (the token-efficient machine substrate, matching the server); `--pretty` indents it for reading and implies `--json`. JSON semantics are identical either way — only whitespace differs — so any parser is unaffected. - JSON property projection on the list views: `index`, `catalog`, and `files` take `--fields a,b` (emit only these properties) or `--except a,b` (emit all but these), so an agent never pays tokens for fields it will not read. The flags are mutually exclusive, imply `--json`, match property names case-insensitively, and reject an unknown name (exit 2) listing the valid ones; `okf index --no-body` is shorthand for dropping the `body` field. - Bundled companion agent skill (`okf skill `): SKILL.md carrying the judgment (the CLI surface stays self-describing via `--help`) — including the orient-before-you-read protocol and the CLI/judgment boundary — the OKF v0.1 spec, authoring and CLI references (tag-vocabulary curation, the SPEC-section map, the closeout gate), and concept/index/log templates. - Runs on Ruby >= 2.4 with two runtime dependencies: rack and webrick. [1.13.0]: https://github.com/serradura/okf-gem/compare/v1.12.0...v1.13.0 [1.12.0]: https://github.com/serradura/okf-gem/compare/v1.11.0...v1.12.0 [1.11.0]: https://github.com/serradura/okf-gem/compare/v1.10.0...v1.11.0 [1.10.0]: https://github.com/serradura/okf-gem/compare/v1.9.0...v1.10.0 [1.9.0]: https://github.com/serradura/okf-gem/compare/v1.8.0...v1.9.0 [1.8.0]: https://github.com/serradura/okf-gem/compare/v1.7.0...v1.8.0 [1.7.0]: https://github.com/serradura/okf-gem/compare/v1.6.0...v1.7.0 [1.6.0]: https://github.com/serradura/okf-gem/compare/v1.5.0...v1.6.0 [1.5.0]: https://github.com/serradura/okf-gem/compare/v1.4.0...v1.5.0 [1.4.0]: https://github.com/serradura/okf-gem/compare/v1.3.0...v1.4.0 [1.3.0]: https://github.com/serradura/okf-gem/compare/v1.2.0...v1.3.0 [1.2.0]: https://github.com/serradura/okf-gem/compare/v1.1.0...v1.2.0 [1.1.0]: https://github.com/serradura/okf-gem/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/serradura/okf-gem/releases/tag/v1.0.0