okf 1.10.0 → 1.12.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.
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
-
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 |
56
-
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`.
34
+ One install carries the whole workflow, and that is the point of a single gem:
91
35
 
92
- ```bash
93
- gem install okf # with Ruby
94
- curl -fsSL https://docker.okfgem.com/install.sh | sh # no Ruby? Docker
95
- ```
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.
96
39
 
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,154 +107,173 @@ 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.
191
111
 
192
- ## Installation
112
+ ## Try it in four steps
193
113
 
194
- > **In Claude Code**, the plugin is the fastest path: two commands install the whole
195
- > toolchain (skill, `/okf:gem`, and the curation hook). See
196
- > [Claude Code plugin](#claude-code-plugin). Everywhere else, install the gem:
114
+ From zero to your first bundle.
197
115
 
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 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
116
+ **1. Get the `okf` command.** Two ways in; either one puts `okf` on your `PATH`.
201
117
 
202
118
  ```bash
203
- gem install okf
204
- # or, in a project
205
- bundle add okf
119
+ gem install okf # with Ruby
120
+ curl -fsSL https://docker.okfgem.com/install.sh | sh # no Ruby? Docker
206
121
  ```
207
122
 
208
- From a checkout, this builds the gem and installs it into your Ruby environment,
209
- putting the `okf` command on your `PATH`:
123
+ **2. Install the skill.** Teach your agent the format Claude Code, or any
124
+ other agent.
210
125
 
211
126
  ```bash
212
- bundle exec rake install
127
+ okf skill .claude # or: okf skill .agents
213
128
  ```
214
129
 
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`:
130
+ **3. Start an agent session** where your project lives.
219
131
 
220
132
  ```bash
221
- # validate / lint / search / index … mirror the CLI, over the mounted bundle
222
- docker run --rm -v "$PWD:/data" ghcr.io/serradura/okf validate .
133
+ claude
134
+ ```
223
135
 
224
- # serve the live graph: bind 0.0.0.0 so the host can reach it, and publish the port
225
- docker run --rm -v "$PWD:/data" -p 8808:8808 ghcr.io/serradura/okf server . --bind 0.0.0.0
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
226
142
  ```
227
143
 
228
- A container has to bind `0.0.0.0` for the host to reach it at all, which is
229
- exactly the bind that makes the registry read-only. Reading the graph is
230
- unaffected, and there is no flag that opens the registry on that bind it is a
231
- per-user file, managed from the machine that owns it or from `okf registry`.
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.
232
149
 
233
- Then open <http://127.0.0.1:8808>. Images are published for `linux/amd64` and
234
- `linux/arm64` on
235
- [ghcr.io](https://github.com/serradura/okf-gem/pkgs/container/okf): `:latest`
236
- tracks the newest release, or pin a version like `:1.10.0`.
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>
237
158
 
238
- Tired of the long line? Install a Docker-backed [`okf` command](https://docker.okfgem.com),
239
- so every verb drops the `docker run` prefix and reads exactly like the native CLI
240
- (mount, port, and bind handled for you). Do this only on a machine without the
241
- gem:
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.
192
+
193
+ ## Installation
194
+
195
+ > **In Claude Code**, the plugin is the fastest path: two commands install the whole
196
+ > toolchain (skill, `/okf:gem`, and the curation hook). See
197
+ > [Claude Code plugin](#claude-code-plugin). Everywhere else, install the gem:
242
198
 
243
199
  ```bash
244
- curl -fsSL https://docker.okfgem.com/install.sh | sh # or grab the script by hand
245
- okf validate .
246
- okf server .
200
+ gem install okf
201
+ # or, in a project
202
+ bundle add okf
247
203
  ```
248
204
 
249
- On Windows the image runs under Docker Desktop (WSL2); install with PowerShell
250
- instead: `irm https://docker.okfgem.com/install.ps1 | iex`.
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.
251
207
 
252
- ## Command line
208
+ ### No Ruby? Use Docker
253
209
 
254
- These verbs are written to be read by an **agent first and a person second** —
255
- that is what the skill drives, with no wrapper in between. Every read verb takes
256
- `--json`, `index`/`catalog`/`files` project down to the fields you ask for
257
- (`--fields`/`--except`), so nothing pays for output it will not read, and the
258
- exit codes are stable enough to branch on in CI. The same commands render as
259
- scannable plain text when a human is the one looking.
210
+ The official image bundles the CLI, so every `okf` command runs against a bundle
211
+ you mount at `/data`:
260
212
 
261
213
  ```bash
262
- okf validate <dir|@slug> [--json] # check OKF v0.1 conformance (§9)
263
- okf lint <dir|@slug> [--json] [--fail-on warn] [...] # report curation-quality issues
264
- okf loose <dir|@slug> [--json] # list files with no graph links, by folder
265
- okf search <dir|@slug…|@all> <term…> [--regexp|--fuzzy] # ranked retrieval; @slugs or @all span bundles
266
- okf index <dir|@slug> [--json] [--area A] [--no-body] # progressive-disclosure map (§6): bodies, rollups, listings
267
- okf server [DIR|@slug…] [-p PORT] [--bind ADDR] [...] # serve one bundle, or many behind a hub (⌘K searches every one)
268
- okf render <dir|@slug> [-o FILE] [--layout NAME] [...] # export the graph as one static, self-contained HTML file
269
- okf registry list [--json] # list registered bundles (* marks the default)
270
- okf registry set <dir|@slug> [--as SLUG] [--default] # add or update a bundle (a bare `server` serves it)
271
- okf registry del <dir|@slug> # remove a bundle from the registry
272
- okf registry default <@slug> | rename <@slug> <new> # move a bundle to the front (the default) / rename it
273
-
274
- # The registry is a plain JSON file at $OKF_HOME/registry.json (default ~/.okf).
275
- # It is ordered, and the first entry is the default: `registry default @slug`
276
- # moves that entry to the front. The subcommand leads and flags follow it:
277
- # `registry set <dir> --as X`, never `registry --json set <dir>`.
278
- # A running server reads it at boot — restart after changes.
279
- # Behind a multi-bundle server, /b/ lists every bundle and ⌘/Ctrl-K switches.
280
- # @slug names a registered bundle instead of a path — the slug from `registry
281
- # set`, or bare @ for the default. Anywhere a <dir> goes, an @slug goes:
282
- # okf lint @handbook works from anywhere. Set $OKF_HOME to point every verb
283
- # at another registry.
284
- okf graph <dir|@slug> [--json] [--minimal] [--hubs] # print the knowledge graph (--hubs ranks by inbound links)
285
- okf catalog | files | tags | types | stats <dir|@slug> [--json] # the browser views, on the CLI
286
- # `tags --by type|area` regroups the tag index per concept dimension; each row
287
- # shows count/total, so a tag confined to one area (a domain) reads differently
288
- # from one spread across several (a cross-cutting concern).
289
- okf skill <dest> [--here] [--force] # install the companion agent skill
290
- okf --version
214
+ docker run --rm -v "$PWD:/data" ghcr.io/serradura/okf validate .
215
+ docker run --rm -v "$PWD:/data" -p 8808:8808 ghcr.io/serradura/okf server . --bind 0.0.0.0
291
216
  ```
292
217
 
293
- Exit codes: `0` success, `1` non-conformant bundle (or a `lint --fail-on`
294
- threshold crossed), `2` usage error.
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:
295
220
 
296
221
  ```bash
297
- $ okf validate docs
298
- OKF v0.1 conformance — docs
299
- concepts: 37 index.md: 10 log.md: 1
300
- ! warn features/link-suggestions.md: cross-link target not found: `/graph-view.md` (tolerated under §5.3)
301
-
302
- ✓ conformant (33 warning(s))
222
+ curl -fsSL https://docker.okfgem.com/install.sh | sh # PowerShell: irm https://docker.okfgem.com/install.ps1 | iex
223
+ okf validate .
224
+ okf server .
225
+ ```
303
226
 
304
- $ okf server docs
305
- serving 37 concepts at http://127.0.0.1:8808 (Ctrl-C to stop)
227
+ Images are published for `linux/amd64` and `linux/arm64` on
228
+ [ghcr.io](https://github.com/serradura/okf-gem/pkgs/container/okf).
306
229
 
307
- $ okf render docs > public/index.html # the same page, static — host it anywhere
308
- ```
230
+ ## Where to go next
231
+
232
+ Installed. The rest of this page is each surface the gem gives you over a bundle,
233
+ in the order most people meet them:
234
+
235
+ - **[The graph](#the-graph)** — the whole bundle on one page, live or baked into a
236
+ single HTML file you can host anywhere. Start here if you want to *see* it.
237
+ - **[Agent skill](#agent-skill)** — the verbs your agent runs to author, maintain
238
+ and answer from a bundle, so you stay the editor rather than the typist.
239
+ - **[Claude Code plugin](#claude-code-plugin)** — that skill, a slash command and a
240
+ post-edit curation hook, in two lines.
241
+ - **[Command line](#command-line)** — every view as scannable text or as JSON, with
242
+ exit codes stable enough to gate CI on.
243
+ - **[Library](#library)** — `OKF::Bundle` in your own Ruby, and the graph as a Rack
244
+ app you can mount in an app you already have.
245
+ - **[Extending okf](#extending-okf-and-running-it-safely)** — ship a verb as a gem,
246
+ and what the page does and does not trust in a bundle you did not write.
247
+
248
+ Full reference for every verb and flag lives in
249
+ **[the docs](https://okfgem.com/docs/)**.
250
+
251
+ ## The graph
309
252
 
310
253
  <picture>
311
254
  <source media="(prefers-color-scheme: dark)" srcset=".github/server-dark.png">
312
- <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.">
255
+ <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.">
313
256
  </picture>
314
257
 
315
- _The graph server on this repo's own [`.okf`](.okf) bundle, with the
316
- `capabilities/graph-server` concept selected. Try it live at
258
+ _The graph server on this repo's own [`.okf`](.okf) bundle, with the `overview`
259
+ concept selected. Try it live at
317
260
  **[demo.okfgem.com](https://demo.okfgem.com)**._
318
261
 
319
- The page is one template from a phone to a desktop: on small screens the
320
- navigation rail becomes a drawer, the toolbar folds into a `⚙` sheet, and the
321
- panels go full-bleed rotate a tablet and the layout re-evaluates. On a touch
322
- screen a tap opens a preview card at the bottom edge rather than a panel over
323
- the whole viewport, so the graph stays on screen and live while you read: drag
324
- the card up for the neighbourhood, tap a link in it and it walks there in place.
325
- It is keyboard-first too: `⌘/Ctrl-K` opens a command palette in every mode (views
326
- always; bundles too when a [hub](#one-registry-many-bundles) is serving), `/`
327
- jumps to the current view's search, and `?` answers with a sheet of every
328
- shortcut.
329
-
330
- The search box says what it is doing while it does it: a live `7/8` count of
331
- what the filter kept, and — when a word matches nothing here — a panel naming
332
- the bundle and the query, offering to search every other bundle instead. The
333
- query carries over, so nothing is typed twice.
262
+ One page, from a phone to a desktop: the navigation rail becomes a drawer, the
263
+ toolbar folds into a `⚙` sheet, and a tap opens a preview card at the bottom edge
264
+ rather than a panel over the whole viewport, so the graph stays live while you
265
+ read. Drag the card up for the neighbourhood, tap a link and it walks there in
266
+ place.
267
+
268
+ It is keyboard-first: **`⌘/Ctrl-K`** opens a command palette that searches
269
+ concepts, jumps to a view, and — behind a [hub](#one-registry-many-bundles)
270
+ switches bundles. **`/`** jumps to the current view's search, **`?`** answers with
271
+ every shortcut. Cluster mode boxes the graph by directory and nests as deep as
272
+ your tree does.
334
273
 
335
274
  To skip the server entirely, **`okf render <dir>`** writes that same page as one
336
- self-contained HTML file, the whole bundle baked in, so you can publish the
337
- graph on GitHub Pages or any static host.
275
+ self-contained HTML file, the whole bundle baked in, so you can publish the graph
276
+ on GitHub Pages or any static host.
338
277
 
339
278
  ### One registry, many bundles
340
279
 
@@ -350,97 +289,44 @@ okf search @all rate limit # ranked retrieval across every register
350
289
  okf server # no args: the whole registry behind one hub
351
290
  ```
352
291
 
353
- The first entry still on disk is the **default** the bundle a bare `okf
354
- server` opens at `/` and a bare `@` names; `okf registry default @slug` moves an
355
- entry to the front. Behind the hub each bundle mounts at `/b/<slug>/`, and the
356
- `⌘/Ctrl-K` palette both switches bundles and **searches every one of them at
357
- once** — type a few words and the matching concepts appear with their bundle and
358
- a snippet, from wherever you are.
359
-
360
- The in the rail opens **Bundles** the registry on the graph page itself, so
361
- switching the default, renaming an entry or dropping one no longer means finding
362
- `/b/` first. Each row carries its size, its health as a *word*, and which one `/`
363
- opens. There is no Add: registering a bundle means naming a filesystem path,
364
- which a browser cannot hand over and an agent can (`okf registry set <dir>`), and
365
- the panel says so instead of leaving the absence to be noticed.
366
-
367
- `/b/` is the **bundles list**: every bundle with its size, its health
368
- (conformance plus curation, so a warning is a warning and not a failure), the
369
- default marked, and any entry whose folder has gone missing shown rather than
370
- quietly dropped.
371
-
372
- Managing the registry without a terminal happens on the graph page instead, in
373
- the **Bundles** panel make default, rename, remove, where you are already
374
- reading. Those controls are the one thing that does not follow you onto a
375
- network: bind anywhere but loopback and they are refused outright, with no flag
376
- that says otherwise, since `--bind 0.0.0.0` is how a personal tool becomes a
377
- public one. `--read-only` declines them on loopback too, and is the only switch
378
- there is. Writes rebuild the hub's set as they go, so a rename takes effect on
379
- the next click; a registry change made *elsewhere* while it runs still wants a
380
- restart to be served. Set `$OKF_HOME` to point every verb at a different registry.
381
-
382
- `graph` and `server` are best-effort (§9): a file with invalid frontmatter is
383
- skipped (and noted on stderr), not fatal, so one bad file never breaks the rest.
384
- The [graph server](.okf/capabilities/graph-server.md) concept walks the request
385
- flow and endpoints; the [server trust boundary](#server-trust-boundary) below
386
- explains how the page handles a bundle you did not author.
387
-
388
- `lint` (the [curation linter](.okf/capabilities/linter.md)) reports curation
389
- quality (reachability, backlog, completeness, freshness, provenance, and hygiene)
390
- separately from `validate`. It is advisory: it exits
391
- `0` even with findings unless you opt into gating with `--fail-on warn`.
392
-
393
- ```bash
394
- $ okf lint docs
395
- OKF lint — docs
396
- concepts: 37 edges: 87 index.md: 10 log.md: 1
397
- hubs: features/chat/sources/source-ingestion-pipeline (×12), …
398
-
399
- Backlog
400
- · info graph-view.md: referenced by 3 link(s) across 2 concept(s) but does not exist
401
- ! warn features/index.md: index links to missing concept `../../CHANGELOG.md`
402
- Completeness
403
- · info features/bundles/entry-editor.md: missing recommended field: description
404
- Hygiene
405
- ! warn link-suggestions.md: reference-style link `[:approved_ids]` has no matching definition (an invisible broken link)
406
-
407
- ⚠ 3 warn, 31 info
408
- ```
409
-
410
- That `hubs:` line is the bundle's centre of gravity — the concepts the most links
411
- point *at*, with their inbound count. **`okf graph <dir> --hubs`** expands it into
412
- the full ranking and, for each hub, the areas those links come *from*:
413
-
414
- ```bash
415
- $ okf graph docs --hubs
416
- Hubs — docs (38 of 40 concepts with inbound links)
417
-
418
- storage/reconcile ×16 storage 6, shape 4, exploration 3, (root) 1, …
419
- ```
420
-
421
- That second half is what decides whether a concept sits where it belongs: a hub
422
- pulled in mostly from outside its own folder is usually homed by history rather
423
- than by meaning. Together with `okf tags <dir> --by area`, it is the evidence the
424
- skill's [`refine`](#agent-skill) verb reads before proposing anything.
425
-
426
- `loose` lists the files that float in the graph: concepts with no cross-links
427
- in or out (graph degree 0), grouped by folder. It is a curation lens over
428
- `lint`'s `unlinked` check, distinct from `orphan`. An `index.md` listing makes a
429
- file _reachable_ (not an orphan) but is not a graph edge, so a listed file
430
- can still be loose. A loose file may be fine: a terminal leaf like a backlog
431
- item is loose by design. `loose` surfaces the set for you to judge and
432
- always exits `0`.
292
+ Related bundles can share a name: `okf registry group backend @handbook @runbooks`
293
+ makes `@backend` stand for the set (members can be groups too, so they nest), and
294
+ `okf search @backend rate limit` or `okf server @backend` then targets all of them
295
+ at once a durable subset for the two verbs that take several bundles.
296
+
297
+ The registry lives under `$OKF_HOME` (default `~/.okf`) — one per user. For one
298
+ scoped to a single project instead, `okf registry init` drops a
299
+ `.okf-registry.json` in the current directory; okf then discovers it by walking up
300
+ from wherever you run, and every registry op and every `@slug` resolves through
301
+ it in place of the global one. So a bare `okf server` inside that repo serves *its*
302
+ bundles with no `$OKF_HOME` setup. The nearest registry wins, and
303
+ `OKF_NO_DISCOVERY=1` forces the global one.
304
+
305
+ Commit that file and it travels with the repo: a bundle under the project root is
306
+ stored relative to the registry, so a checkout on another machine or a container
307
+ that mounts the repo resolves the same bundles unchanged. (Bundles outside the
308
+ tree keep absolute paths, which do not travel.)
309
+
310
+ Behind the hub each bundle mounts at `/b/<slug>/`, `/b/` lists them all, and the
311
+ `⌘/Ctrl-K` palette both switches bundles and **searches every one at once** type
312
+ a few words and the matching concepts appear with their bundle and a snippet, from
313
+ wherever you are.
314
+
315
+ The rail opens **Bundles**, the registry on the graph page itself: make
316
+ default, rename, remove, where you are already reading. Those controls are the one
317
+ thing that does not follow you onto a network bind anywhere but loopback and
318
+ they are refused outright, since `--bind 0.0.0.0` is how a personal tool becomes a
319
+ public one.
433
320
 
434
321
  ## Agent skill
435
322
 
436
323
  The gem carries the [companion OKF agent skill](.okf/capabilities/agent-skill.md):
437
- a `SKILL.md` plus reference
438
- and template files that teach a coding agent to author, maintain, and consume OKF
439
- bundles and to drive the commands above. Because the skill ships inside the gem,
440
- installing the gem already puts the skill on your machine, and the skill's
441
- CLI reference can never drift from the executable it was released with.
442
- Using Claude Code? The [plugin](#claude-code-plugin) below installs this same
443
- skill plus a post-edit curation hook.
324
+ a `SKILL.md` plus reference and template files that teach a coding agent to
325
+ author, maintain, and consume OKF bundles and to drive the
326
+ [commands below](#command-line).
327
+ Because the skill ships inside the gem, installing the gem already puts the skill
328
+ on your machine, and the skill's CLI reference can never drift from the
329
+ executable it was released with.
444
330
 
445
331
  The skill routes a small set of verbs. In Claude Code they run as `/okf:gem
446
332
  <verb>`; used standalone, the skill infers the verb from your request.
@@ -452,7 +338,7 @@ The skill routes a small set of verbs. In Claude Code they run as `/okf:gem
452
338
  | `produce` | Create or extend a bundle from code, docs, or knowledge in people's heads |
453
339
  | `migrate` | Adopt existing Markdown docs in place: frontmatter and reserved files added, bodies kept verbatim |
454
340
  | `maintain` | Sync the bundle's content with reality after the code or docs change |
455
- | `refine` | Restructure it for retrieval: evidence-first, cohesion over balance — proposes, never applies |
341
+ | `refine` | Restructure it for retrieval: evidence-first, cohesion over balance — proposes, never applies |
456
342
  | `consume` | Use the bundle as context for a task, writing back what you learn |
457
343
  | `curate` | Structural upkeep as it stands: `validate` + `lint` + `loose` |
458
344
  | `doctor` | Install and verify the CLI, then doctor the bundle |
@@ -463,194 +349,169 @@ first: **`curate`** keeps the bundle *sound* (the structure as it stands),
463
349
  **`maintain`** keeps it *true* (the code changed, so the content must catch up),
464
350
  and **`refine`** changes *where knowledge lives* — the folder a concept sits in,
465
351
  a fact re-explained in three overviews. Reach for `refine` when nothing is wrong
466
- and everything is hard to find: a folder grown to twenty concepts, a hub homed by
467
- history, tags that neither connect nor mark. It reads the evidence, then hands
468
- you a proposal — it never rearranges your bundle on its own.
352
+ and everything is hard to find. It reads the evidence, then hands you a proposal
353
+ it never rearranges your bundle on its own.
469
354
 
470
- Point it at your agent's config directory (or its skills directory) and the tree
471
- settles in its own `skills/okf/` folder, so a shared skills directory never gets
472
- the files loose:
355
+ Point it at your agent's config directory and the tree settles in its own
356
+ `skills/okf/` folder, so a shared skills directory never gets the files loose:
473
357
 
474
358
  ```bash
475
359
  okf skill .claude # Claude Code -> .claude/skills/okf
476
360
  okf skill .agents # agent-agnostic -> .agents/skills/okf
477
361
  ```
478
362
 
479
- The destination is required (no default). The skill lands in `<dest>/skills/okf`,
480
- unless `<dest>` already ends in `skills` (→ `<dest>/okf`) or `okf` (used as-is).
481
- Pass `--here` to paste the tree straight into `<dest>`, wherever it is. The
482
- resolved directory must be empty unless you pass `--force`, so a customized skill
483
- is never clobbered.
363
+ The resolved directory must be empty unless you pass `--force`, so a customized
364
+ skill is never clobbered.
484
365
 
485
- ## Library
366
+ ## Claude Code plugin
486
367
 
487
- The gem (the [library API](.okf/capabilities/library-api.md)) is two layers: pure
488
- in-memory data (`OKF::Concept`, `OKF::Bundle`)
489
- you build, interrogate, and analyze with no disk involved, and on-disk
490
- handles (`OKF::Concept::File`, `OKF::Bundle::Folder`) that add
491
- load/save/reload/delete, an "ActiveRecord for the filesystem".
368
+ This repository doubles as a Claude Code plugin marketplace, so the whole
369
+ toolchain installs with two commands inside Claude Code:
492
370
 
493
- ### Pure, in-memory (no disk)
371
+ ```
372
+ /plugin marketplace add serradura/okf-gem
373
+ /plugin install okf@okfgem
374
+ ```
494
375
 
495
- Build knowledge straight from data, with no markdown round-trip, and run every
496
- feature against it. This is the surface an embedding app (e.g. a Rails store)
497
- uses to reuse the gem over knowledge it already holds as records:
376
+ The plugin carries three pieces: the [`okf` skill](#agent-skill) above;
377
+ **`/okf:gem`**, a front door that hands its arguments to the skill unchanged (no
378
+ arguments: it orients on your bundle and recommends the next move, never
379
+ auto-runs); and a **curation hook** that runs `okf validate` + `okf lint` after
380
+ every edit inside a bundle and returns the findings as context. The checks are
381
+ the CLI's own, so the feedback is deterministic.
498
382
 
499
- ```ruby
500
- require "okf"
383
+ The hook stays silent outside bundles, and it is config-free to switch off:
384
+ `OKF_CURATE_DISABLED=1` turns it off, `OKF_CURATE_QUIET=1` keeps the findings
385
+ without the install suggestion, and an `<!-- okf-disable -->` comment skips one
386
+ file.
501
387
 
502
- concept = OKF::Concept.new(
503
- path: "tables/orders.md",
504
- frontmatter: { "type" => "BigQuery Table", "title" => "Orders" },
505
- body: "Joined with [customers](/tables/customers.md).\n"
506
- )
507
-
508
- concept.id # => "tables/orders"
509
- concept.links # => ["/tables/customers.md"] (spec §5 cross-links)
510
- concept.citations # => [...] (spec §8 # Citations)
511
- concept.external_links # => [...] (URLs / mailto:)
512
- concept.to_markdown # => String (inverse of OKF::Markdown::Frontmatter.parse)
513
- concept.lint # => OKF::Bundle::Linter::Report (the concept-scoped checks)
514
-
515
- bundle = OKF::Bundle.new(concepts: [ concept ]) # also: reserved:, unparseable:
516
- bundle.validate # => OKF::Bundle::Validator::Result (spec §9 conformance)
517
- bundle.lint # => OKF::Bundle::Linter::Report (curation quality)
518
- bundle.graph # => OKF::Bundle::Graph (#nodes, #edges, #to_h)
519
- ```
388
+ Prefer no plugin? `gem install okf && okf skill .claude` installs the skill
389
+ alone, and the skill itself instructs the agent to run the same checks after
390
+ editing a bundle.
520
391
 
521
- ### On disk
392
+ ## Command line
522
393
 
523
- `OKF::Bundle::Folder` reads a directory into a pure bundle and materializes one
524
- back; `OKF::Concept::File` is a single-file handle:
394
+ These verbs are written to be read by an **agent first and a person second** —
395
+ that is what the skill drives, with no wrapper in between. Every read verb takes
396
+ `--json`, the list views project down to the fields you ask for
397
+ (`--fields`/`--except`), so nothing pays for output it will not read, and the
398
+ exit codes are stable enough to branch on in CI. The same commands render as
399
+ scannable plain text when a human is the one looking.
525
400
 
526
- ```ruby
527
- folder = OKF::Bundle::Folder.load("docs")
528
- folder.bundle # => OKF::Bundle (the pure bundle it read)
529
- folder.concepts # => [OKF::Concept] (reserved files excluded)
530
- folder.validate; folder.lint; folder.graph # delegate to the pure core
531
- folder.concept("tables/orders") # => OKF::Concept::File
401
+ ```bash
402
+ okf validate <dir|@slug> # is this legal OKF?
403
+ okf lint <dir|@slug> [--fail-on warn] # is it navigable, complete, fresh?
404
+ okf loose <dir|@slug> # concepts with no links in or out
405
+ okf search <dir|@slug…|@all> <term…> # ranked retrieval; @all spans every bundle
406
+ okf index <dir|@slug> [--dir D] [--depth N] # the §6 map: index bodies, rollups, listings
407
+ okf dirs <dir|@slug> [--dir D] [--depth N] # the shape: every directory and what it holds
408
+ okf catalog | files | tags | types | stats <dir|@slug> # the browser views, on the CLI
409
+ okf graph <dir|@slug> [--hubs] [--traffic] # the raw graph; --hubs ranks concepts, --traffic dirs
410
+ okf server [DIR|@slug…] [-p PORT] [--bind ADDR] # the live graph: one bundle, or all of them
411
+ okf render <dir|@slug> [-o FILE] # the same page as one static, self-contained file
412
+ okf registry init | list | set | del | default | rename | group | ungroup # name & group your bundles; @slug works anywhere
413
+ okf skill <dest> # install the companion agent skill
414
+ okf --version
415
+ ```
532
416
 
533
- require "okf/server/app" # the server loads on demand, like the CLI does
534
- OKF::Server::App.new(folder) # => a Rack app: the interactive graph server
417
+ Exit codes: `0` success, `1` non-conformant bundle (or a `lint --fail-on`
418
+ threshold crossed), `2` usage error. Every flag is in `okf <verb> --help` and in
419
+ [the docs](https://okfgem.com/docs/).
420
+
421
+ ```bash
422
+ $ okf validate docs
423
+ OKF v0.1 conformance — docs
424
+ concepts: 37 index.md: 10 log.md: 1
425
+ ! warn features/link-suggestions.md: cross-link target not found: `/graph-view.md` (tolerated under §5.3)
426
+
427
+ ✓ conformant (33 warning(s))
535
428
 
536
- # build in memory, then write it out (validates §9 before publishing):
537
- OKF::Bundle::Folder.new(bundle: bundle, root: "out/dir").save
429
+ $ okf server docs
430
+ serving 37 concepts at http://127.0.0.1:8808 (Ctrl-C to stop)
538
431
 
539
- file = OKF::Concept::File.read(root: "docs", path: "tables/orders.md")
540
- file.concept # => OKF::Concept (pure)
541
- file.save; file.delete; file.reload
432
+ $ okf render docs > public/index.html # the same page, static — host it anywhere
542
433
  ```
543
434
 
544
- The lower-level pieces are usable on their own too: `OKF::Bundle::Validator.call(bundle)`,
545
- `OKF::Bundle::Linter.call(bundle, min_body: 50)`, `OKF::Bundle::Graph.build(bundle)`,
546
- `OKF::Markdown::Frontmatter.parse(markdown)`. Mounting the graph in Rails, auth
547
- included: the [Rails guide](https://okfgem.com/docs/guides/rails/) walks it.
548
-
549
- ### Conformance model
550
-
551
- `validate` (the [conformance validator](.okf/capabilities/validator.md)) implements
552
- the spec's [§9 conformance definition](lib/okf/skill/reference/SPEC.md#9-conformance)
553
- exactly. There are three hard conditions, all errors:
554
-
555
- - **§9.1** every non-reserved file has a parseable YAML frontmatter block;
556
- - **§9.2** every such block has a non-empty `type`;
557
- - **§9.3** every `index.md`/`log.md` present follows §6/§7: a nested `index.md`
558
- has no frontmatter, a root `index.md` carries only `okf_version`, and `log.md`
559
- date headings are ISO `YYYY-MM-DD`.
560
-
561
- Everything the spec marks as soft guidance is a warning and never makes a
562
- bundle non-conformant: missing recommended fields, non-list tags, an unparseable
563
- timestamp, and broken cross-links (§5.3), which consumers MUST tolerate.
564
- `OKF::Bundle::Folder#save` validates before publishing, so it never writes a
565
- bundle that fails §9.
566
-
567
- ### Curation model (`lint`)
568
-
569
- `validate` asks _"is this §9-conformant?"_ and is forbidden by §9 to reject for
570
- broken links or missing optional fields. `lint` asks the complementary question,
571
- _"is this well-curated, navigable, trustworthy?"_, over exactly those tolerated
572
- things. The two stay separate: `lint` has its own `OKF::Bundle::Linter` and
573
- report, never emits conformance errors, and is advisory unless you pass
574
- `--fail-on warn`.
575
-
576
- Checks span six categories: **reachability** (orphans, not-in-index, disconnected
577
- islands, unlinked), **backlog** (demand-ranked missing concepts, broken index entries),
578
- **completeness** (stubs, missing title/description/timestamp), **freshness**
579
- (`--stale-after`), **provenance** (uncited external claims, broken citations,
580
- spec §8), and **hygiene** (duplicate titles, unused/undefined reference links,
581
- self-links). Select with `--only`/`--except`; `--json` emits the full report as JSON.
582
-
583
- Two loop concerns from the format's own guidance, _contradictions_ and _semantic_
584
- staleness, need to understand meaning and are not computed here; `lint --json`
585
- is the structured input an agent consumes to reason about those.
586
-
587
- ## Extending okf
588
-
589
- This is the other kind of plugin, and it is worth separating from the
590
- [Claude Code plugin](#claude-code-plugin) below: that one teaches an *agent* to
591
- use okf, while this one adds behavior to the `okf` command everyone runs.
592
-
593
- Publish a gem named `okf-*` that carries an `okf/plugin.rb`, and installing it is
594
- the whole installation: your verb answers to `okf`, appears in `okf help` under
595
- `installed extensions:`, and behaves like a built-in — nothing to register,
596
- nothing to configure, no list of known addons in this gem. The same seam takes
597
- retrieval backends, which is what `okf search --engine NAME` chooses between.
598
-
599
- Two promises make that safe to install: nothing an addon registers can displace a
600
- built-in, and a broken addon is skipped and reported rather than taking the CLI
601
- down with it.
602
-
603
- The [extension points](.okf/design/extension-points.md) concept has the contract
604
- and the threat model; `test/integration/cli/cli_plugin_test.rb` is a working
605
- example to copy.
606
-
607
- ## Server trust boundary
608
-
609
- > [!NOTE]
610
- > The graph page defends against a bundle that carries active content in two
611
- > places. It escapes any graph data inlined into the page (`<` becomes `&lt;`),
612
- > which keeps that data from breaking out of its `<script>`. And it fetches each
613
- > concept body on demand, then runs marked's HTML output through
614
- > [DOMPurify](https://github.com/cure53/DOMPurify) before it reaches the DOM, so
615
- > a script or event handler hidden in a Markdown body is stripped rather than
616
- > executed. Descriptions are escaped on the server.
617
- >
618
- > That covers the paths that could run code. The page still loads Cytoscape,
619
- > marked, and DOMPurify from a CDN and renders whatever links and diagrams a body
620
- > contains, so treat an unfamiliar bundle the way you would treat any document
621
- > from a source you do not know.
622
-
623
- The [server trust boundary](.okf/design/server-trust-boundary.md) concept has the
624
- full write-up: the two data paths, what sanitizing does, and what it leaves to
625
- your judgment.
435
+ ### Reading a big bundle a level at a time
626
436
 
627
- ## Claude Code plugin
437
+ A few hundred concepts is a map nobody reads whole, so `index` and `dirs` descend
438
+ instead of dumping. `--dir` takes a directory **and everything under it**,
439
+ `--depth N` bounds how far below that it goes, and the two compose the way you
440
+ actually walk a tree:
628
441
 
629
- This repository doubles as a Claude Code plugin marketplace, so the whole
630
- toolchain installs with two commands inside Claude Code:
442
+ ```bash
443
+ okf dirs @handbook # the shape: every dir, what it holds directly and below
444
+ okf index @handbook --depth 1 --no-body # the top of the map, no prose
445
+ okf index @handbook --dir platform/api # now open one branch — with the chain that places it
446
+ ```
447
+
448
+ Naming a `--dir` brings its ancestors along, marked `↑`, so a branch is never
449
+ shown adrift of the context that says what it is — the root `index.md`'s prose
450
+ first among it.
631
451
 
452
+ For an agent the saving is the whole point. On a 400-concept bundle the full
453
+ `okf index --json` is 313 KB; the skeleton it orients on is 2.8 KB:
454
+
455
+ ```bash
456
+ okf index @handbook --json --depth 1 --except body,listing
632
457
  ```
633
- /plugin marketplace add serradura/okf-gem
634
- /plugin install okf@okfgem
458
+
459
+ ## Library
460
+
461
+ `require "okf"` gives you the whole thing as Ruby objects — two layers: pure
462
+ in-memory data (`OKF::Concept`, `OKF::Bundle`) you build and analyze with no disk
463
+ involved, and on-disk handles (`OKF::Concept::File`, `OKF::Bundle::Folder`) that
464
+ add load/save/reload/delete, an "ActiveRecord for the filesystem".
465
+
466
+ ```ruby
467
+ require "okf"
468
+
469
+ folder = OKF::Bundle::Folder.load("docs")
470
+ folder.concepts # => [OKF::Concept]
471
+ folder.validate # => §9 conformance result
472
+ folder.lint # => curation report
473
+ folder.graph # => nodes, edges, indexes
474
+
475
+ require "okf/server/app"
476
+ OKF::Server::App.new(folder) # => a Rack app: the interactive graph, mountable
635
477
  ```
636
478
 
637
- The plugin carries three pieces:
479
+ That last line is the point of the Rack app: the graph mounts inside an app you
480
+ already have, auth included. The [Rails guide](https://okfgem.com/docs/guides/rails/)
481
+ walks it, and the [library API](https://okfgem.com/docs/library/) covers
482
+ the pure layer, the writer, and the lower-level pieces.
638
483
 
639
- | Piece | What it does |
640
- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
641
- | `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`). |
642
- | `/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). |
643
- | 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. |
484
+ ### validate and lint are two different questions
644
485
 
645
- The hook stays silent outside bundles, and when the CLI is missing it suggests
646
- `/okf:gem` once per session instead of failing on each edit. It is config-free to
647
- switch off: set `OKF_CURATE_DISABLED=1` to turn it off, `OKF_CURATE_QUIET=1` to
648
- keep the findings but drop that suggestion, or drop an `<!-- okf-disable -->`
649
- comment into a file to skip curation for that one.
486
+ `validate` (the [conformance validator](https://okfgem.com/docs/cli/validate/)) asks
487
+ _"is this legal OKF?"_ and implements the spec's
488
+ [§9](lib/okf/skill/reference/SPEC.md#9-conformance) exactly which means it is
489
+ *forbidden* to reject a bundle for a broken link or a missing optional field.
650
490
 
651
- Prefer no plugin? `gem install okf && okf skill .claude` installs the skill
652
- alone, and the skill itself instructs the agent to run the same checks after
653
- editing a bundle.
491
+ `lint` (the [curation linter](https://okfgem.com/docs/cli/lint/)) asks the
492
+ complementary question, _"is this well-curated, navigable, trustworthy?"_, over
493
+ exactly those tolerated things: reachability, backlog, completeness, freshness,
494
+ provenance, hygiene. It is advisory and exits `0` even with findings unless you
495
+ pass `--fail-on warn`.
496
+
497
+ Keeping them apart is what lets you gate CI on conformance without gating it on
498
+ taste. `lint --json` is also the structured input an agent reads to reason about
499
+ the two things no checker can compute — contradictions, and *semantic* staleness.
500
+
501
+ ## Extending okf, and running it safely
502
+
503
+ Publish a gem named `okf-*` carrying an `okf/plugin.rb` and installing it is the
504
+ whole installation: your verb answers to `okf` and behaves like a built-in.
505
+ Nothing an addon registers can displace one, and a broken addon is skipped rather
506
+ than taking the CLI down. Contract and threat model:
507
+ [extension points](.okf/design/extension-points.md).
508
+
509
+ The graph page treats a bundle as untrusted content: inlined data is escaped, and
510
+ every concept body is sanitized before it reaches the DOM, so a script hidden in
511
+ Markdown is stripped rather than run. It still loads libraries from a CDN, so
512
+ treat an unfamiliar bundle the way you would treat any document from a source you
513
+ do not know. Full write-up:
514
+ [server trust boundary](.okf/design/server-trust-boundary.md).
654
515
 
655
516
  ## Development
656
517
 
@@ -668,6 +529,10 @@ docker run --rm -v "$PWD":/src:ro ruby:2.4 bash -c \
668
529
  "cp -a /src /build && cd /build && rm -f Gemfile.lock && bundle install --quiet && bundle exec rake test"
669
530
  ```
670
531
 
532
+ The graph page has its own suite in a real browser (`bundle exec rake
533
+ browser:setup`, then `rake test:browser`). See [AGENTS.md](AGENTS.md) for the
534
+ maintainer guide.
535
+
671
536
  ## Contributing
672
537
 
673
538
  Bug reports and pull requests are welcome on GitHub at