okf 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +696 -133
  3. data/README.md +250 -334
  4. data/lib/okf/bundle/folder.rb +24 -5
  5. data/lib/okf/bundle/linter.rb +1 -1
  6. data/lib/okf/bundle/search/index.rb +13 -3
  7. data/lib/okf/bundle/search.rb +91 -11
  8. data/lib/okf/bundle.rb +26 -2
  9. data/lib/okf/cli/catalog.rb +66 -0
  10. data/lib/okf/cli/command.rb +657 -0
  11. data/lib/okf/cli/dirs.rb +118 -0
  12. data/lib/okf/cli/files.rb +68 -0
  13. data/lib/okf/cli/graph.rb +82 -0
  14. data/lib/okf/cli/index.rb +169 -0
  15. data/lib/okf/cli/lint.rb +139 -0
  16. data/lib/okf/cli/loose.rb +78 -0
  17. data/lib/okf/cli/registry.rb +229 -0
  18. data/lib/okf/cli/render.rb +66 -0
  19. data/lib/okf/cli/search.rb +285 -0
  20. data/lib/okf/cli/server.rb +186 -0
  21. data/lib/okf/cli/skill.rb +57 -0
  22. data/lib/okf/cli/stats.rb +113 -0
  23. data/lib/okf/cli/tags.rb +144 -0
  24. data/lib/okf/cli/types.rb +37 -0
  25. data/lib/okf/cli/validate.rb +66 -0
  26. data/lib/okf/cli.rb +425 -1706
  27. data/lib/okf/render/graph/template.html.erb +1285 -129
  28. data/lib/okf/render/graph.rb +46 -2
  29. data/lib/okf/server/app.rb +71 -4
  30. data/lib/okf/server/hub/not_found.rb +663 -0
  31. data/lib/okf/server/hub.rb +512 -38
  32. data/lib/okf/skill/SKILL.md +26 -19
  33. data/lib/okf/skill/playbooks/consume.md +3 -3
  34. data/lib/okf/skill/playbooks/curate.md +3 -1
  35. data/lib/okf/skill/playbooks/maintain.md +7 -5
  36. data/lib/okf/skill/playbooks/menu.md +5 -0
  37. data/lib/okf/skill/playbooks/refine.md +93 -0
  38. data/lib/okf/skill/playbooks/search.md +7 -7
  39. data/lib/okf/skill/reference/cli.md +122 -22
  40. data/lib/okf/version.rb +1 -1
  41. data/lib/okf.rb +9 -0
  42. metadata +38 -8
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <a href="https://okfgem.com">
3
3
  <picture>
4
4
  <source media="(prefers-color-scheme: dark)" srcset=".github/hero-dark.png">
5
- <img src=".github/hero-light.png" width="100%" alt="okf-gem: the complete harness for the Open Knowledge Format. An Agent Skill, a CLI and library, and a Graph let your agent author, curate, and consume your project's knowledge. 100% local.">
5
+ <img src=".github/hero-light.png" width="100%" alt="okf-gem, the Open Knowledge Format toolkit. Everything OKF, in one gem: author, curate, and consume your project's knowledge, with your agent. 100% local. Start at https://okfgem.com/#try. Three pieces in one install: the Agent Skill (the brain) authors, curates and consumes; the CLI/Lib (the muscle) validates, lints and does full-text search; the Graph (the dashboard) searches and explores it, live or static. Available from RubyGems, as a Docker image, and as a Claude Code plugin.">
6
6
  </picture>
7
7
  </a>
8
8
  </p>
@@ -26,100 +26,20 @@
26
26
  <b><a href="https://docker.okfgem.com">Docker image</a></b>
27
27
  </p>
28
28
 
29
- **okf-gem** (`okf` on RubyGems) is the complete harness for
30
- **Open Knowledge Format (OKF)** v0.1 bundles: create, maintain, and consume
31
- your project's knowledge with your agent. The package is **Agent Skill + CLI/Lib + Graph**: an agent skill that authors and curates, a CLI and Ruby library that validate, lint, search, and embed, and a graph to explore, live or static, in one gem that runs 100% local. A bundle is a directory of Markdown files with YAML frontmatter that humans and agents read from one source; each file is a _concept_. The gem does not define a new place to keep knowledge; it gives you leverage over knowledge that already lives as Markdown.
29
+ **okf-gem** (`okf` on RubyGems or Docker) gives your project's knowledge one
30
+ durable home in your repo, in Markdown your team and your agents both read: the
31
+ decisions and the reasoning an agent cannot re-derive from the code, versioned
32
+ beside the code they explain.
32
33
 
33
- The package, end to end:
34
+ One install carries the whole workflow, and that is the point of a single gem:
34
35
 
35
- <p align="center">
36
- <picture>
37
- <source media="(prefers-color-scheme: dark)" srcset=".github/overview-dark.png">
38
- <img src=".github/overview-light.png" width="760" alt="The package: the Agent Skill (your coding agent authors and curates, you stay the editor) writes and maintains the bundle, a folder of Markdown + YAML in your repo where one concept is one file and links between files are the knowledge graph. The bundle is read by the CLI/Lib (validate: legal OKF per section 9; lint: well-curated and fresh; search: ranked retrieval; require okf for Ruby objects) and by the Graph, in four modes: okf server (a live local server), okf render (the same page exported as one static, self-contained HTML file you can host anywhere), okf registry (every registered bundle behind one hub), and OKF::Server::App (the Rack app mounted in a Rails route). One gem, 100% local, Ruby 2.4 or newer, only rack, webrick and minifts as dependencies.">
39
- </picture>
40
- </p>
41
-
42
- Over a bundle the gem gives you the `okf`
43
- command-line tool (the library API is also usable in-process). Each capability
44
- below links to the concept that documents it: this gem's own knowledge is an OKF
45
- bundle, so you can read its design in the format it defends.
46
-
47
- | Capability | What it answers | Verb |
48
- | ------------------------------------------------------------- | --------------------------------- | ---------------- |
49
- | [Companion agent skill](.okf/capabilities/agent-skill.md) | Can an agent author it? | `skill` |
50
- | [Conformance validator](.okf/capabilities/validator.md) | Is this a legal OKF bundle? (§9) | `validate` |
51
- | [Curation linter](.okf/capabilities/linter.md) | Is it navigable, complete, fresh? | `lint` / `loose` |
52
- | [Ranked text search](.okf/capabilities/search.md) | Which concept covers X? | `search` |
53
- | [Interactive graph server](.okf/capabilities/graph-server.md) | Can I explore it visually? | `server` |
54
- | [Static render](.okf/capabilities/render.md) | Can I ship a serverless snapshot? | `render` |
55
- | [Library API](.okf/capabilities/library-api.md) | Can my Ruby program use it? | in-process |
36
+ - an **Agent Skill**, so your agent writes and curates the knowledge instead of you;
37
+ - a **CLI and Ruby library**, so it stays correct: validated, linted, and searchable in milliseconds;
38
+ - a **Graph**, so anyone can see the shape of what the team knows, live or as one static file you can host anywhere.
56
39
 
57
- And because knowledge rarely lives in one bundle, a per-user
58
- [registry](.okf/registry.md) gives each bundle a name: `okf registry set ./docs`
59
- once, then `@docs` works anywhere a `<dir>` does — from any directory — and a
60
- bare `okf server` hosts every registered bundle behind one hub.
61
-
62
- > [!TIP]
63
- > **Browse the gem as knowledge, not just docs.** This README is the front door;
64
- > the depth lives in the [`.okf/`](.okf) bundle this repo ships. Start at the
65
- > [overview](.okf/overview.md), then follow the graph into the
66
- > [capabilities](.okf/capabilities/) (what it does), the
67
- > [design constraints](.okf/design/) (why it stays this light), and the
68
- > [format itself](.okf/format/) (what it operates on). Run `okf server .okf` to
69
- > walk the same bundle as an interactive graph.
70
-
71
- It is deliberately light so it runs on the Ruby your OS already ships:
72
-
73
- - works on every Ruby since 2.4, the same floor as [rack](https://github.com/rack/rack),
74
- its core dependency;
75
- - only three runtime dependencies: `rack` (the server is a mountable Rack app),
76
- `webrick` (unbundled from Ruby in 3.0), and
77
- [`minifts`](https://github.com/serradura/minifts) (the search engine — pure
78
- Ruby, no dependencies of its own, same 2.4 floor);
79
- - no ActiveSupport, no native extension, no build step, no JavaScript
80
- toolchain — the [design constraints](.okf/design/) that hold this line are
81
- enforced by tests.
82
-
83
- That range is not aspirational: CI runs the full test suite and RuboCop on every
84
- one of these on each push.
85
-
86
- ## Try it in four steps
87
-
88
- From zero to your first bundle.
89
-
90
- **1. Get the `okf` command.** Two ways in; either one puts `okf` on your `PATH`.
91
-
92
- ```bash
93
- gem install okf # with Ruby
94
- curl -fsSL https://docker.okfgem.com/install.sh | sh # no Ruby? Docker
95
- ```
96
-
97
- **2. Install the skill.** Teach your agent the format — Claude Code, or any
98
- other agent.
99
-
100
- ```bash
101
- okf skill .claude # or: okf skill .agents
102
- ```
103
-
104
- **3. Start an agent session** where your project lives.
105
-
106
- ```bash
107
- claude
108
- ```
109
-
110
- **4. Make your first bundle.** Two ways in, by what you already have: docs keep
111
- every word, code gets written up for you.
112
-
113
- ```
114
- /okf migrate <path-to-your-docs> # have docs? adopted in place, bodies verbatim
115
- /okf produce based on <path-to-your-code> # only code? the skill authors the concepts
116
- ```
117
-
118
- > [!TIP]
119
- > **Once you have a bundle**, run `/okf maintain` in the agent session to keep it
120
- > in sync as the code changes, and `okf server <folder>` to explore it as a graph.
121
- > In Claude Code, the [plugin](#claude-code-plugin) adds a post-edit curation hook
122
- > that runs `validate` + `lint` for you.
40
+ The package is **Agent Skill + CLI/Lib + Graph**. It runs 100% local, adds no
41
+ service to your stack, and does not define a new place to keep knowledge: it
42
+ gives you leverage over the Markdown you already have.
123
43
 
124
44
  ## Why OKF
125
45
 
@@ -187,7 +107,88 @@ timestamp: 2026-07-11T12:00:00Z
187
107
  ```
188
108
 
189
109
  That bundle is this gem's own documentation. Clone the repo and run
190
- `okf server .okf` to browse it as the graph diagrammed at the top of this file.
110
+ `okf server .okf` to browse it as an interactive graph.
111
+
112
+ ## Try it in four steps
113
+
114
+ From zero to your first bundle.
115
+
116
+ **1. Get the `okf` command.** Two ways in; either one puts `okf` on your `PATH`.
117
+
118
+ ```bash
119
+ gem install okf # with Ruby
120
+ curl -fsSL https://docker.okfgem.com/install.sh | sh # no Ruby? Docker
121
+ ```
122
+
123
+ **2. Install the skill.** Teach your agent the format — Claude Code, or any
124
+ other agent.
125
+
126
+ ```bash
127
+ okf skill .claude # or: okf skill .agents
128
+ ```
129
+
130
+ **3. Start an agent session** where your project lives.
131
+
132
+ ```bash
133
+ claude
134
+ ```
135
+
136
+ **4. Make your first bundle.** Two ways in, by what you already have: docs keep
137
+ every word, code gets written up for you.
138
+
139
+ ```
140
+ /okf migrate <path-to-your-docs> # have docs? adopted in place, bodies verbatim
141
+ /okf produce based on <path-to-your-code> # only code? the skill authors the concepts
142
+ ```
143
+
144
+ > [!TIP]
145
+ > **Once you have a bundle**, run `/okf maintain` in the agent session to keep it
146
+ > in sync as the code changes, and `okf server <folder>` to explore it as a graph.
147
+ > In Claude Code, the [plugin](#claude-code-plugin) adds a post-edit curation hook
148
+ > that runs `validate` + `lint` for you.
149
+
150
+ The package, end to end:
151
+
152
+ <p align="center">
153
+ <picture>
154
+ <source media="(prefers-color-scheme: dark)" srcset=".github/overview-dark.png">
155
+ <img src=".github/overview-light.png" width="760" alt="The package: the Agent Skill (your coding agent authors and curates, you stay the editor) writes and maintains the bundle, a folder of Markdown + YAML in your repo where one concept is one file and links between files are the knowledge graph. The bundle is read by the CLI/Lib (validate: legal OKF per section 9; lint: well-curated and fresh; search: ranked retrieval; require okf for Ruby objects) and by the Graph, in four modes: okf server (a live local server), okf render (the same page exported as one static, self-contained HTML file you can host anywhere), okf registry (every registered bundle behind one hub), and OKF::Server::App (the Rack app mounted in a Rails route). One gem, 100% local, Ruby 2.4 or newer, only rack, webrick and minifts as dependencies.">
156
+ </picture>
157
+ </p>
158
+
159
+ What the gem does, and which verb does it. This table is the map; **[the
160
+ docs](https://okfgem.com/docs/)** are the manual.
161
+
162
+ | Capability | What it answers | Verb |
163
+ | ------------------------------------------------------------- | --------------------------------- | -------------------------- |
164
+ | [Companion agent skill](https://okfgem.com/docs/skill/) | Can an agent author it? | `skill` |
165
+ | [Conformance validator](https://okfgem.com/docs/cli/validate/) | Is this a legal OKF bundle? | `validate` |
166
+ | [Curation linter](https://okfgem.com/docs/cli/lint/) | Is it navigable, complete, fresh? | `lint` / `loose` |
167
+ | [Ranked text search](https://okfgem.com/docs/cli/search/) | Which concept covers X? | `search` |
168
+ | [Read views](https://okfgem.com/docs/cli/) | What is in here, and where? | `index` / `dirs` / `catalog` |
169
+ | [Interactive graph server](https://okfgem.com/docs/cli/server/) | Can I explore it visually? | `server` |
170
+ | [Static render](https://okfgem.com/docs/cli/render/) | Can I ship a serverless snapshot? | `render` |
171
+ | [Library API](https://okfgem.com/docs/library/) | Can my Ruby program use it? | in-process |
172
+
173
+ And because knowledge rarely lives in one bundle, a per-user
174
+ [registry](.okf/registry.md) gives each bundle a name: `okf registry set ./docs`
175
+ once, then `@docs` works anywhere a `<dir>` does — from any directory — and a
176
+ bare `okf server` hosts every registered bundle behind one hub.
177
+
178
+ > [!TIP]
179
+ > **Browse the gem as knowledge, not just docs.** This README is the front door;
180
+ > the depth lives in the [`.okf/`](.okf) bundle this repo ships. Start at the
181
+ > [overview](.okf/overview.md), then follow the graph into the
182
+ > [capabilities](.okf/capabilities/) (what it does), the
183
+ > [design constraints](.okf/design/) (why it stays this light), and the
184
+ > [format itself](.okf/format/) (what it operates on). Run `okf server .okf` to
185
+ > walk the same bundle as an interactive graph.
186
+
187
+ **It installs on the Ruby your OS already ships** — every Ruby since 2.4, three
188
+ small dependencies, no native extension and no build step — so there is nothing
189
+ to provision and nothing to keep up to date. The
190
+ [design constraints](.okf/design/) that hold that line are enforced by tests on
191
+ every supported Ruby.
191
192
 
192
193
  ## Installation
193
194
 
@@ -195,88 +196,65 @@ That bundle is this gem's own documentation. Clone the repo and run
195
196
  > toolchain (skill, `/okf:gem`, and the curation hook). See
196
197
  > [Claude Code plugin](#claude-code-plugin). Everywhere else, install the gem:
197
198
 
198
- | Ruby version | 2.4 | 2.5 | 2.6 | 2.7 | 3.0 | 3.1 | 3.2 | 3.3 | 3.4 | 4.0 |
199
- | ---------------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
200
- | Tested/Supported | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
201
-
202
199
  ```bash
203
200
  gem install okf
204
201
  # or, in a project
205
202
  bundle add okf
206
203
  ```
207
204
 
208
- From a checkout, this builds the gem and installs it into your Ruby environment,
209
- putting the `okf` command on your `PATH`:
205
+ Tested and supported on every Ruby from **2.4 through 4.0**. From a checkout,
206
+ `bundle exec rake install` builds and installs it locally.
210
207
 
211
- ```bash
212
- bundle exec rake install
213
- ```
208
+ ### No Ruby? Use Docker
214
209
 
215
- ### Run it with Docker (no Ruby needed)
216
-
217
- Prefer not to install Ruby? The official image bundles the CLI, so every `okf`
218
- command runs against a bundle you mount at `/data`:
210
+ The official image bundles the CLI, so every `okf` command runs against a bundle
211
+ you mount at `/data`:
219
212
 
220
213
  ```bash
221
- # validate / lint / search / index … mirror the CLI, over the mounted bundle
222
214
  docker run --rm -v "$PWD:/data" ghcr.io/serradura/okf validate .
223
-
224
- # serve the live graph: bind 0.0.0.0 so the host can reach it, and publish the port
225
215
  docker run --rm -v "$PWD:/data" -p 8808:8808 ghcr.io/serradura/okf server . --bind 0.0.0.0
226
216
  ```
227
217
 
228
- Then open <http://127.0.0.1:8808>. Images are published for `linux/amd64` and
229
- `linux/arm64` on
230
- [ghcr.io](https://github.com/serradura/okf-gem/pkgs/container/okf): `:latest`
231
- tracks the newest release, or pin a version like `:1.9.0`.
232
-
233
- Tired of the long line? Install a Docker-backed [`okf` command](https://docker.okfgem.com),
234
- so every verb drops the `docker run` prefix and reads exactly like the native CLI
235
- (mount, port, and bind handled for you). Do this only on a machine without the
236
- gem:
218
+ Tired of the long line? The Docker-backed [`okf` command](https://docker.okfgem.com)
219
+ drops the prefix so every verb reads exactly like the native CLI:
237
220
 
238
221
  ```bash
239
- curl -fsSL https://docker.okfgem.com/install.sh | sh # or grab the script by hand
222
+ curl -fsSL https://docker.okfgem.com/install.sh | sh # PowerShell: irm https://docker.okfgem.com/install.ps1 | iex
240
223
  okf validate .
241
224
  okf server .
242
225
  ```
243
226
 
244
- On Windows the image runs under Docker Desktop (WSL2); install with PowerShell
245
- instead: `irm https://docker.okfgem.com/install.ps1 | iex`.
227
+ Images are published for `linux/amd64` and `linux/arm64` on
228
+ [ghcr.io](https://github.com/serradura/okf-gem/pkgs/container/okf).
246
229
 
247
230
  ## Command line
248
231
 
232
+ These verbs are written to be read by an **agent first and a person second** —
233
+ that is what the skill drives, with no wrapper in between. Every read verb takes
234
+ `--json`, the list views project down to the fields you ask for
235
+ (`--fields`/`--except`), so nothing pays for output it will not read, and the
236
+ exit codes are stable enough to branch on in CI. The same commands render as
237
+ scannable plain text when a human is the one looking.
238
+
249
239
  ```bash
250
- okf validate <dir|@slug> [--json] # check OKF v0.1 conformance (§9)
251
- okf lint <dir|@slug> [--json] [--fail-on warn] [...] # report curation-quality issues
252
- okf loose <dir|@slug> [--json] # list files with no graph links, by folder
253
- okf search <dir|@slug…|@all> <term…> [-e|--fuzzy] [...] # ranked retrieval; @slugs or @all span bundles
254
- okf index <dir|@slug> [--json] [--area A] [--no-body] # progressive-disclosure map (§6): bodies, rollups, listings
255
- okf server [DIR|@slug…] [-p PORT] [--bind ADDR] [...] # serve one bundle, or many behind a hub (⌘K to switch)
256
- okf render <dir|@slug> [-o FILE] [--layout NAME] [...] # export the graph as one static, self-contained HTML file
257
- okf registry list [--json] # list registered bundles (* marks the default)
258
- okf registry set <dir|@slug> [--as SLUG] [--default] # add or update a bundle (a bare `server` serves it)
259
- okf registry del <dir|@slug> # remove a bundle from the registry
260
- okf registry default <@slug> | rename <@slug> <new> # move a bundle to the front (the default) / rename it
261
-
262
- # The registry is a plain JSON file at $OKF_HOME/registry.json (default ~/.okf).
263
- # It is ordered, and the first entry is the default: `registry default @slug`
264
- # moves that entry to the front. The subcommand leads and flags follow it:
265
- # `registry set <dir> --as X`, never `registry --json set <dir>`.
266
- # A running server reads it at boot — restart after changes.
267
- # Behind a multi-bundle server, /b/ lists every bundle and ⌘/Ctrl-K switches.
268
- # @slug names a registered bundle instead of a path — the slug from `registry
269
- # set`, or bare @ for the default. Anywhere a <dir> goes, an @slug goes:
270
- # okf lint @handbook works from anywhere. Set $OKF_HOME to point every verb
271
- # at another registry.
272
- okf graph <dir|@slug> [--json] [--minimal] [--no-body] # print the knowledge graph
273
- okf catalog | files | tags | types | stats <dir|@slug> [--json] # the browser views, on the CLI
274
- okf skill <dest> [--here] [--force] # install the companion agent skill
240
+ okf validate <dir|@slug> # is this legal OKF?
241
+ okf lint <dir|@slug> [--fail-on warn] # is it navigable, complete, fresh?
242
+ okf loose <dir|@slug> # concepts with no links in or out
243
+ okf search <dir|@slug…|@all> <term…> # ranked retrieval; @all spans every bundle
244
+ okf index <dir|@slug> [--dir D] [--depth N] # the §6 map: index bodies, rollups, listings
245
+ okf dirs <dir|@slug> [--dir D] [--depth N] # the shape: every directory and what it holds
246
+ okf catalog | files | tags | types | stats <dir|@slug> # the browser views, on the CLI
247
+ okf graph <dir|@slug> [--hubs] # the raw graph; --hubs ranks by inbound links
248
+ okf server [DIR|@slug…] [-p PORT] [--bind ADDR] # the live graph: one bundle, or all of them
249
+ okf render <dir|@slug> [-o FILE] # the same page as one static, self-contained file
250
+ okf registry list | set | del | default | rename # name your bundles (see below)
251
+ okf skill <dest> # install the companion agent skill
275
252
  okf --version
276
253
  ```
277
254
 
278
255
  Exit codes: `0` success, `1` non-conformant bundle (or a `lint --fail-on`
279
- threshold crossed), `2` usage error.
256
+ threshold crossed), `2` usage error. Every flag is in `okf <verb> --help` and in
257
+ [the docs](https://okfgem.com/docs/).
280
258
 
281
259
  ```bash
282
260
  $ okf validate docs
@@ -292,26 +270,56 @@ serving 37 concepts at http://127.0.0.1:8808 (Ctrl-C to stop)
292
270
  $ okf render docs > public/index.html # the same page, static — host it anywhere
293
271
  ```
294
272
 
273
+ ### Reading a big bundle a level at a time
274
+
275
+ A few hundred concepts is a map nobody reads whole, so `index` and `dirs` descend
276
+ instead of dumping. `--dir` takes a directory **and everything under it**,
277
+ `--depth N` bounds how far below that it goes, and the two compose the way you
278
+ actually walk a tree:
279
+
280
+ ```bash
281
+ okf dirs @handbook # the shape: every dir, what it holds directly and below
282
+ okf index @handbook --depth 1 --no-body # the top of the map, no prose
283
+ okf index @handbook --dir platform/api # now open one branch — with the chain that places it
284
+ ```
285
+
286
+ Naming a `--dir` brings its ancestors along, marked `↑`, so a branch is never
287
+ shown adrift of the context that says what it is — the root `index.md`'s prose
288
+ first among it.
289
+
290
+ For an agent the saving is the whole point. On a 400-concept bundle the full
291
+ `okf index --json` is 313 KB; the skeleton it orients on is 2.8 KB:
292
+
293
+ ```bash
294
+ okf index @handbook --json --depth 1 --except body,listing
295
+ ```
296
+
297
+ ## The graph
298
+
295
299
  <picture>
296
300
  <source media="(prefers-color-scheme: dark)" srcset=".github/server-dark.png">
297
- <img src=".github/server-light.png" alt="The okf graph server: a force-directed knowledge graph with a concept selected, its neighbors highlighted, and the inspector panel showing the concept's type, tags, cross-links, and rendered Markdown body.">
301
+ <img src=".github/server-light.png" alt="The okf graph server: a force-directed knowledge graph with a concept selected, its neighbors highlighted and the rest of the bundle dimmed, and the inspector panel showing the concept's type, description, tags, and every concept it links to and from, each labelled with its type.">
298
302
  </picture>
299
303
 
300
- _The graph server on this repo's own [`.okf`](.okf) bundle, with the
301
- `capabilities/graph-server` concept selected. Try it live at
304
+ _The graph server on this repo's own [`.okf`](.okf) bundle, with the `overview`
305
+ concept selected. Try it live at
302
306
  **[demo.okfgem.com](https://demo.okfgem.com)**._
303
307
 
304
- The page is one template from a phone to a desktop: on small screens the
305
- navigation rail becomes a drawer, the toolbar folds into a `⚙` sheet, and the
306
- panels go full-bleed rotate a tablet and the layout re-evaluates. It is
307
- keyboard-first too: `⌘/Ctrl-K` opens a command palette in every mode (views
308
- always; bundles too when a [hub](#one-registry-many-bundles) is serving), `/`
309
- jumps to the current view's search, and `?` answers with a sheet of every
310
- shortcut.
308
+ One page, from a phone to a desktop: the navigation rail becomes a drawer, the
309
+ toolbar folds into a `⚙` sheet, and a tap opens a preview card at the bottom edge
310
+ rather than a panel over the whole viewport, so the graph stays live while you
311
+ read. Drag the card up for the neighbourhood, tap a link and it walks there in
312
+ place.
313
+
314
+ It is keyboard-first: **`⌘/Ctrl-K`** opens a command palette that searches
315
+ concepts, jumps to a view, and — behind a [hub](#one-registry-many-bundles) —
316
+ switches bundles. **`/`** jumps to the current view's search, **`?`** answers with
317
+ every shortcut. Cluster mode boxes the graph by directory and nests as deep as
318
+ your tree does.
311
319
 
312
320
  To skip the server entirely, **`okf render <dir>`** writes that same page as one
313
- self-contained HTML file, the whole bundle baked in, so you can publish the
314
- graph on GitHub Pages or any static host.
321
+ self-contained HTML file, the whole bundle baked in, so you can publish the graph
322
+ on GitHub Pages or any static host.
315
323
 
316
324
  ### One registry, many bundles
317
325
 
@@ -327,59 +335,25 @@ okf search @all rate limit # ranked retrieval across every register
327
335
  okf server # no args: the whole registry behind one hub
328
336
  ```
329
337
 
330
- The first entry still on disk is the **default** the bundle a bare `okf
331
- server` opens at `/` and a bare `@` names; `okf registry default @slug` moves an
332
- entry to the front. Behind the hub each bundle mounts at `/b/<slug>/`, `/b/` is
333
- a browsable index, and the `⌘/Ctrl-K` palette switches bundles without leaving
334
- the page. The hub reads the registry at boot, so restart it after registry
335
- changes; set `$OKF_HOME` to point every verb at a different registry.
336
-
337
- `graph` and `server` are best-effort (§9): a file with invalid frontmatter is
338
- skipped (and noted on stderr), not fatal, so one bad file never breaks the rest.
339
- The [graph server](.okf/capabilities/graph-server.md) concept walks the request
340
- flow and endpoints; the [server trust boundary](#server-trust-boundary) below
341
- explains how the page handles a bundle you did not author.
338
+ Behind the hub each bundle mounts at `/b/<slug>/`, `/b/` lists them all, and the
339
+ `⌘/Ctrl-K` palette both switches bundles and **searches every one at once** type
340
+ a few words and the matching concepts appear with their bundle and a snippet, from
341
+ wherever you are.
342
342
 
343
- `lint` (the [curation linter](.okf/capabilities/linter.md)) reports curation
344
- quality (reachability, backlog, completeness, freshness, provenance, and hygiene)
345
- separately from `validate`. It is advisory: it exits
346
- `0` even with findings unless you opt into gating with `--fail-on warn`.
347
-
348
- ```bash
349
- $ okf lint docs
350
- OKF lint — docs
351
- concepts: 37 edges: 87 index.md: 10 log.md: 1
352
- hubs: features/chat/sources/source-ingestion-pipeline (×12), …
353
-
354
- Backlog
355
- · info graph-view.md: referenced by 3 link(s) across 2 concept(s) but does not exist
356
- ! warn features/index.md: index links to missing concept `../../CHANGELOG.md`
357
- Completeness
358
- · info features/bundles/entry-editor.md: missing recommended field: description
359
- Hygiene
360
- ! warn link-suggestions.md: reference-style link `[:approved_ids]` has no matching definition (an invisible broken link)
361
-
362
- ⚠ 3 warn, 31 info
363
- ```
364
-
365
- `loose` lists the files that float in the graph: concepts with no cross-links
366
- in or out (graph degree 0), grouped by folder. It is a curation lens over
367
- `lint`'s `unlinked` check, distinct from `orphan`. An `index.md` listing makes a
368
- file _reachable_ (not an orphan) but is not a graph edge, so a listed file
369
- can still be loose. A loose file may be fine: a terminal leaf like a backlog
370
- item is loose by design. `loose` surfaces the set for you to judge and
371
- always exits `0`.
343
+ The ⚙ rail opens **Bundles**, the registry on the graph page itself: make
344
+ default, rename, remove, where you are already reading. Those controls are the one
345
+ thing that does not follow you onto a network — bind anywhere but loopback and
346
+ they are refused outright, since `--bind 0.0.0.0` is how a personal tool becomes a
347
+ public one.
372
348
 
373
349
  ## Agent skill
374
350
 
375
351
  The gem carries the [companion OKF agent skill](.okf/capabilities/agent-skill.md):
376
- a `SKILL.md` plus reference
377
- and template files that teach a coding agent to author, maintain, and consume OKF
378
- bundles and to drive the commands above. Because the skill ships inside the gem,
379
- installing the gem already puts the skill on your machine, and the skill's
380
- CLI reference can never drift from the executable it was released with.
381
- Using Claude Code? The [plugin](#claude-code-plugin) below installs this same
382
- skill plus a post-edit curation hook.
352
+ a `SKILL.md` plus reference and template files that teach a coding agent to
353
+ author, maintain, and consume OKF bundles and to drive the commands above.
354
+ Because the skill ships inside the gem, installing the gem already puts the skill
355
+ on your machine, and the skill's CLI reference can never drift from the
356
+ executable it was released with.
383
357
 
384
358
  The skill routes a small set of verbs. In Claude Code they run as `/okf:gem
385
359
  <verb>`; used standalone, the skill infers the verb from your request.
@@ -391,147 +365,87 @@ The skill routes a small set of verbs. In Claude Code they run as `/okf:gem
391
365
  | `produce` | Create or extend a bundle from code, docs, or knowledge in people's heads |
392
366
  | `migrate` | Adopt existing Markdown docs in place: frontmatter and reserved files added, bodies kept verbatim |
393
367
  | `maintain` | Sync the bundle's content with reality after the code or docs change |
368
+ | `refine` | Restructure it for retrieval: evidence-first, cohesion over balance — proposes, never applies |
394
369
  | `consume` | Use the bundle as context for a task, writing back what you learn |
395
370
  | `curate` | Structural upkeep as it stands: `validate` + `lint` + `loose` |
396
371
  | `doctor` | Install and verify the CLI, then doctor the bundle |
397
372
  | `<okf-cli-verb>` | Run any CLI verb (`validate`, `lint`, `search`, `index`, `server`, the read views) and interpret its output |
398
373
 
399
- Point it at your agent's config directory (or its skills directory) and the tree
400
- settles in its own `skills/okf/` folder, so a shared skills directory never gets
401
- the files loose:
374
+ Three of those look alike and are not, which is the distinction worth learning
375
+ first: **`curate`** keeps the bundle *sound* (the structure as it stands),
376
+ **`maintain`** keeps it *true* (the code changed, so the content must catch up),
377
+ and **`refine`** changes *where knowledge lives* — the folder a concept sits in,
378
+ a fact re-explained in three overviews. Reach for `refine` when nothing is wrong
379
+ and everything is hard to find. It reads the evidence, then hands you a proposal
380
+ — it never rearranges your bundle on its own.
381
+
382
+ Point it at your agent's config directory and the tree settles in its own
383
+ `skills/okf/` folder, so a shared skills directory never gets the files loose:
402
384
 
403
385
  ```bash
404
386
  okf skill .claude # Claude Code -> .claude/skills/okf
405
387
  okf skill .agents # agent-agnostic -> .agents/skills/okf
406
388
  ```
407
389
 
408
- The destination is required (no default). The skill lands in `<dest>/skills/okf`,
409
- unless `<dest>` already ends in `skills` (→ `<dest>/okf`) or `okf` (used as-is).
410
- Pass `--here` to paste the tree straight into `<dest>`, wherever it is. The
411
- resolved directory must be empty unless you pass `--force`, so a customized skill
412
- is never clobbered.
390
+ The resolved directory must be empty unless you pass `--force`, so a customized
391
+ skill is never clobbered.
413
392
 
414
393
  ## Library
415
394
 
416
- The gem (the [library API](.okf/capabilities/library-api.md)) is two layers: pure
417
- in-memory data (`OKF::Concept`, `OKF::Bundle`)
418
- you build, interrogate, and analyze with no disk involved, and on-disk
419
- handles (`OKF::Concept::File`, `OKF::Bundle::Folder`) that add
420
- load/save/reload/delete, an "ActiveRecord for the filesystem".
421
-
422
- ### Pure, in-memory (no disk)
423
-
424
- Build knowledge straight from data, with no markdown round-trip, and run every
425
- feature against it. This is the surface an embedding app (e.g. a Rails store)
426
- uses to reuse the gem over knowledge it already holds as records:
395
+ `require "okf"` gives you the whole thing as Ruby objects — two layers: pure
396
+ in-memory data (`OKF::Concept`, `OKF::Bundle`) you build and analyze with no disk
397
+ involved, and on-disk handles (`OKF::Concept::File`, `OKF::Bundle::Folder`) that
398
+ add load/save/reload/delete, an "ActiveRecord for the filesystem".
427
399
 
428
400
  ```ruby
429
401
  require "okf"
430
402
 
431
- concept = OKF::Concept.new(
432
- path: "tables/orders.md",
433
- frontmatter: { "type" => "BigQuery Table", "title" => "Orders" },
434
- body: "Joined with [customers](/tables/customers.md).\n"
435
- )
436
-
437
- concept.id # => "tables/orders"
438
- concept.links # => ["/tables/customers.md"] (spec §5 cross-links)
439
- concept.citations # => [...] (spec §8 # Citations)
440
- concept.external_links # => [...] (URLs / mailto:)
441
- concept.to_markdown # => String (inverse of OKF::Markdown::Frontmatter.parse)
442
- concept.lint # => OKF::Bundle::Linter::Report (the concept-scoped checks)
443
-
444
- bundle = OKF::Bundle.new(concepts: [ concept ]) # also: reserved:, unparseable:
445
- bundle.validate # => OKF::Bundle::Validator::Result (spec §9 conformance)
446
- bundle.lint # => OKF::Bundle::Linter::Report (curation quality)
447
- bundle.graph # => OKF::Bundle::Graph (#nodes, #edges, #to_h)
403
+ folder = OKF::Bundle::Folder.load("docs")
404
+ folder.concepts # => [OKF::Concept]
405
+ folder.validate # => §9 conformance result
406
+ folder.lint # => curation report
407
+ folder.graph # => nodes, edges, indexes
408
+
409
+ require "okf/server/app"
410
+ OKF::Server::App.new(folder) # => a Rack app: the interactive graph, mountable
448
411
  ```
449
412
 
450
- ### On disk
413
+ That last line is the point of the Rack app: the graph mounts inside an app you
414
+ already have, auth included. The [Rails guide](https://okfgem.com/docs/guides/rails/)
415
+ walks it, and the [library API](.okf/capabilities/library-api.md) concept covers
416
+ the pure layer, the writer, and the lower-level pieces.
451
417
 
452
- `OKF::Bundle::Folder` reads a directory into a pure bundle and materializes one
453
- back; `OKF::Concept::File` is a single-file handle:
418
+ ### validate and lint are two different questions
454
419
 
455
- ```ruby
456
- folder = OKF::Bundle::Folder.load("docs")
457
- folder.bundle # => OKF::Bundle (the pure bundle it read)
458
- folder.concepts # => [OKF::Concept] (reserved files excluded)
459
- folder.validate; folder.lint; folder.graph # delegate to the pure core
460
- folder.concept("tables/orders") # => OKF::Concept::File
420
+ `validate` (the [conformance validator](.okf/capabilities/validator.md)) asks
421
+ _"is this legal OKF?"_ and implements the spec's
422
+ [§9](lib/okf/skill/reference/SPEC.md#9-conformance) exactly which means it is
423
+ *forbidden* to reject a bundle for a broken link or a missing optional field.
461
424
 
462
- require "okf/server/app" # the server loads on demand, like the CLI does
463
- OKF::Server::App.new(folder) # => a Rack app: the interactive graph server
425
+ `lint` (the [curation linter](.okf/capabilities/linter.md)) asks the
426
+ complementary question, _"is this well-curated, navigable, trustworthy?"_, over
427
+ exactly those tolerated things: reachability, backlog, completeness, freshness,
428
+ provenance, hygiene. It is advisory and exits `0` even with findings unless you
429
+ pass `--fail-on warn`.
464
430
 
465
- # build in memory, then write it out (validates §9 before publishing):
466
- OKF::Bundle::Folder.new(bundle: bundle, root: "out/dir").save
431
+ Keeping them apart is what lets you gate CI on conformance without gating it on
432
+ taste. `lint --json` is also the structured input an agent reads to reason about
433
+ the two things no checker can compute — contradictions, and *semantic* staleness.
467
434
 
468
- file = OKF::Concept::File.read(root: "docs", path: "tables/orders.md")
469
- file.concept # => OKF::Concept (pure)
470
- file.save; file.delete; file.reload
471
- ```
435
+ ## Extending okf, and running it safely
436
+
437
+ Publish a gem named `okf-*` carrying an `okf/plugin.rb` and installing it is the
438
+ whole installation: your verb answers to `okf` and behaves like a built-in.
439
+ Nothing an addon registers can displace one, and a broken addon is skipped rather
440
+ than taking the CLI down. Contract and threat model:
441
+ [extension points](.okf/design/extension-points.md).
472
442
 
473
- The lower-level pieces are usable on their own too: `OKF::Bundle::Validator.call(bundle)`,
474
- `OKF::Bundle::Linter.call(bundle, min_body: 50)`, `OKF::Bundle::Graph.build(bundle)`,
475
- `OKF::Markdown::Frontmatter.parse(markdown)`. Mounting the graph in Rails, auth
476
- included: the [Rails guide](https://okfgem.com/docs/guides/rails/) walks it.
477
-
478
- ### Conformance model
479
-
480
- `validate` (the [conformance validator](.okf/capabilities/validator.md)) implements
481
- the spec's [§9 conformance definition](lib/okf/skill/reference/SPEC.md#9-conformance)
482
- exactly. There are three hard conditions, all errors:
483
-
484
- - **§9.1** every non-reserved file has a parseable YAML frontmatter block;
485
- - **§9.2** every such block has a non-empty `type`;
486
- - **§9.3** every `index.md`/`log.md` present follows §6/§7: a nested `index.md`
487
- has no frontmatter, a root `index.md` carries only `okf_version`, and `log.md`
488
- date headings are ISO `YYYY-MM-DD`.
489
-
490
- Everything the spec marks as soft guidance is a warning and never makes a
491
- bundle non-conformant: missing recommended fields, non-list tags, an unparseable
492
- timestamp, and broken cross-links (§5.3), which consumers MUST tolerate.
493
- `OKF::Bundle::Folder#save` validates before publishing, so it never writes a
494
- bundle that fails §9.
495
-
496
- ### Curation model (`lint`)
497
-
498
- `validate` asks _"is this §9-conformant?"_ and is forbidden by §9 to reject for
499
- broken links or missing optional fields. `lint` asks the complementary question,
500
- _"is this well-curated, navigable, trustworthy?"_, over exactly those tolerated
501
- things. The two stay separate: `lint` has its own `OKF::Bundle::Linter` and
502
- report, never emits conformance errors, and is advisory unless you pass
503
- `--fail-on warn`.
504
-
505
- Checks span six categories: **reachability** (orphans, not-in-index, disconnected
506
- islands, unlinked), **backlog** (demand-ranked missing concepts, broken index entries),
507
- **completeness** (stubs, missing title/description/timestamp), **freshness**
508
- (`--stale-after`), **provenance** (uncited external claims, broken citations,
509
- spec §8), and **hygiene** (duplicate titles, unused/undefined reference links,
510
- self-links). Select with `--only`/`--except`; `--json` emits the full report as JSON.
511
-
512
- Two loop concerns from the format's own guidance, _contradictions_ and _semantic_
513
- staleness, need to understand meaning and are not computed here; `lint --json`
514
- is the structured input an agent consumes to reason about those.
515
-
516
- ## Server trust boundary
517
-
518
- > [!NOTE]
519
- > The graph page defends against a bundle that carries active content in two
520
- > places. It escapes any graph data inlined into the page (`<` becomes `&lt;`),
521
- > which keeps that data from breaking out of its `<script>`. And it fetches each
522
- > concept body on demand, then runs marked's HTML output through
523
- > [DOMPurify](https://github.com/cure53/DOMPurify) before it reaches the DOM, so
524
- > a script or event handler hidden in a Markdown body is stripped rather than
525
- > executed. Descriptions are escaped on the server.
526
- >
527
- > That covers the paths that could run code. The page still loads Cytoscape,
528
- > marked, and DOMPurify from a CDN and renders whatever links and diagrams a body
529
- > contains, so treat an unfamiliar bundle the way you would treat any document
530
- > from a source you do not know.
531
-
532
- The [server trust boundary](.okf/design/server-trust-boundary.md) concept has the
533
- full write-up: the two data paths, what sanitizing does, and what it leaves to
534
- your judgment.
443
+ The graph page treats a bundle as untrusted content: inlined data is escaped, and
444
+ every concept body is sanitized before it reaches the DOM, so a script hidden in
445
+ Markdown is stripped rather than run. It still loads libraries from a CDN, so
446
+ treat an unfamiliar bundle the way you would treat any document from a source you
447
+ do not know. Full write-up:
448
+ [server trust boundary](.okf/design/server-trust-boundary.md).
535
449
 
536
450
  ## Claude Code plugin
537
451
 
@@ -543,19 +457,17 @@ toolchain installs with two commands inside Claude Code:
543
457
  /plugin install okf@okfgem
544
458
  ```
545
459
 
546
- The plugin carries three pieces:
460
+ The plugin carries three pieces: the [`okf` skill](#agent-skill) above;
461
+ **`/okf:gem`**, a front door that hands its arguments to the skill unchanged (no
462
+ arguments: it orients on your bundle and recommends the next move, never
463
+ auto-runs); and a **curation hook** that runs `okf validate` + `okf lint` after
464
+ every edit inside a bundle and returns the findings as context. The checks are
465
+ the CLI's own, so the feedback is deterministic.
547
466
 
548
- | Piece | What it does |
549
- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
550
- | `okf` skill | The [companion skill](#agent-skill) above, bundled with the plugin (a generated copy that `rake plugin:sync` keeps identical to `lib/okf/skill`). |
551
- | `/okf:gem` | The front door: a pass-through that hands its arguments to the skill unchanged, so the [verb table](#agent-skill) above is the whole routing story — `doctor` installs and verifies the CLI, `curate` runs the full cycle (`validate` + `lint` + `loose`), `produce`/`migrate`/`maintain`/`consume` and any CLI verb do what they say. No arguments: orients on the CLI, the bundle, and what `validate`/`lint` report, then recommends the highest-value next move (never auto-runs). |
552
- | Curation hook | After every Write or Edit inside a bundle, runs `okf validate` + `okf lint` and returns the findings as context. The checks are the CLI's own, so the feedback is deterministic. |
553
-
554
- The hook stays silent outside bundles, and when the CLI is missing it suggests
555
- `/okf:gem` once per session instead of failing on each edit. It is config-free to
556
- switch off: set `OKF_CURATE_DISABLED=1` to turn it off, `OKF_CURATE_QUIET=1` to
557
- keep the findings but drop that suggestion, or drop an `<!-- okf-disable -->`
558
- comment into a file to skip curation for that one.
467
+ The hook stays silent outside bundles, and it is config-free to switch off:
468
+ `OKF_CURATE_DISABLED=1` turns it off, `OKF_CURATE_QUIET=1` keeps the findings
469
+ without the install suggestion, and an `<!-- okf-disable -->` comment skips one
470
+ file.
559
471
 
560
472
  Prefer no plugin? `gem install okf && okf skill .claude` installs the skill
561
473
  alone, and the skill itself instructs the agent to run the same checks after
@@ -577,6 +489,10 @@ docker run --rm -v "$PWD":/src:ro ruby:2.4 bash -c \
577
489
  "cp -a /src /build && cd /build && rm -f Gemfile.lock && bundle install --quiet && bundle exec rake test"
578
490
  ```
579
491
 
492
+ The graph page has its own suite in a real browser (`bundle exec rake
493
+ browser:setup`, then `rake test:browser`). See [AGENTS.md](AGENTS.md) for the
494
+ maintainer guide.
495
+
580
496
  ## Contributing
581
497
 
582
498
  Bug reports and pull requests are welcome on GitHub at