okf 1.5.0 → 1.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +118 -34
- data/lib/okf/cli.rb +32 -0
- data/lib/okf/server/app.rb +30 -0
- data/lib/okf/server/graph/template.html.erb +19 -9
- data/lib/okf/server/graph.rb +25 -9
- data/lib/okf/skill/SKILL.md +19 -16
- data/lib/okf/skill/playbooks/menu.md +5 -2
- data/lib/okf/skill/playbooks/migrate.md +42 -0
- data/lib/okf/skill/playbooks/produce.md +2 -1
- data/lib/okf/skill/reference/cli.md +19 -2
- data/lib/okf/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64b163bdb5b0e234c879f6d5424279b8b329a797292b6322af0379741e06565c
|
|
4
|
+
data.tar.gz: 816d6456345e3cd6e507be229b757f8143987ba2c98ffb1bcc26d45c19bdfba2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ee06f232154bf609ce24882447de92f26e8a33ebddd593ff462bb83ad993d4f79c797a0c88f7647cbea9bba05b190a0b1da127e01e53d99a9b5e99d952eb76b
|
|
7
|
+
data.tar.gz: 69a26c944a1a393f7bcb58cdfd554ed1991c0617fe3fe9d7e138af7dd9847763df947d393a9cea1ff99182430b5f79b488bde78a79f0915740beab088501c4bd
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.7.0] - 2026-07-16
|
|
4
|
+
|
|
5
|
+
- `okf server`: responses are gzipped when the client accepts it
|
|
6
|
+
(`Rack::Deflater` at the boot seam). Lossless and transparent — the browser
|
|
7
|
+
decompresses automatically — and no new dependency, since `Rack::Deflater`
|
|
8
|
+
ships inside rack. Clients that send no `Accept-Encoding` keep getting
|
|
9
|
+
identity responses. `okf render`'s static HTML is untouched.
|
|
10
|
+
- The agent skill gains a `migrate` verb (`playbooks/migrate.md`): convert
|
|
11
|
+
existing documentation into a conformant bundle **in place** — frontmatter
|
|
12
|
+
and reserved files added, bodies kept verbatim (`produce` keeps
|
|
13
|
+
distillation). The verb is routed from SKILL.md's Commands table and intent
|
|
14
|
+
inference, the menu playbook now leads with it when a target already holds
|
|
15
|
+
markdown docs, and pointing any verb at a directory that is not a bundle now
|
|
16
|
+
suggests `migrate` instead of grinding through the validate errors.
|
|
17
|
+
- The graph page's link-preview image points at the renamed
|
|
18
|
+
`okfgem.com/og-demo-v3.png`. The site's OG art was refreshed to drop "Live
|
|
19
|
+
Graph" from the package formula (it is `Agent Skill + CLI/Lib + Graph` now
|
|
20
|
+
that `okf render` makes the graph live *or* static), and the filename carries
|
|
21
|
+
the version so social scrapers pick the new art up.
|
|
22
|
+
- The plugin's `/okf:gem` command is now a pass-through shim: it hands its
|
|
23
|
+
arguments to the okf skill unchanged, making `SKILL.md` the single router
|
|
24
|
+
for every channel. The routing prose the command used to duplicate had no
|
|
25
|
+
drift guard (the sync test covers only the generated skill copy), and the
|
|
26
|
+
not-a-bundle `migrate` suggestion now lives in `SKILL.md`, so standalone
|
|
27
|
+
skill installs get it too.
|
|
28
|
+
|
|
29
|
+
## [1.6.0] - 2026-07-15
|
|
30
|
+
|
|
31
|
+
- New CLI verb: `okf render <dir> [-o FILE]` — the live graph as one static,
|
|
32
|
+
self-contained HTML file, so it hosts where a server can't (GitHub Pages, an
|
|
33
|
+
object store, an attachment). It is the same page `okf server` serves, one
|
|
34
|
+
switch apart: the browser's five on-demand reads — bodies, descriptions,
|
|
35
|
+
catalog, index, logs — now route through named getters that resolve from an
|
|
36
|
+
injected `EMBED` payload instead of the network, so the whole bundle rides
|
|
37
|
+
inside the file with no server and no build step. Prints to stdout (`okf
|
|
38
|
+
render docs > public/index.html`) or writes `-o FILE`. The embedded data is
|
|
39
|
+
`</script>`-escaped exactly like the boot payload and every body still renders
|
|
40
|
+
through `DOMPurify.sanitize(marked.parse(...))`, so the trust boundary holds;
|
|
41
|
+
the trade-off is weight — each body is inlined, so a big bundle makes a big
|
|
42
|
+
file, and `okf server` stays the choice at scale.
|
|
43
|
+
- Official Docker image: `ghcr.io/serradura/okf`, a portable CLI that runs every
|
|
44
|
+
`okf` command (the graph server included) with no Ruby on the host. It is built
|
|
45
|
+
from source and published multi-arch (`linux/amd64`, `linux/arm64`) to the
|
|
46
|
+
GitHub Container Registry on each release tag, so the image always matches the
|
|
47
|
+
gem. Mount a bundle at `/data`; for `server`, add `--bind 0.0.0.0` and publish
|
|
48
|
+
`-p 8808:8808`. See the README's Docker section.
|
|
49
|
+
|
|
3
50
|
## [1.5.0] - 2026-07-13
|
|
4
51
|
|
|
5
52
|
- New CLI verb: `okf search <dir> <term…>` — deterministic ranked retrieval
|
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:
|
|
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.">
|
|
6
6
|
</picture>
|
|
7
7
|
</a>
|
|
8
8
|
</p>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<p align="center">
|
|
11
11
|
<a href="https://rubygems.org/gems/okf"><img src="https://img.shields.io/gem/v/okf" alt="Gem version"></a>
|
|
12
12
|
<a href="https://rubygems.org/gems/okf"><img src="https://img.shields.io/gem/dt/okf" alt="Downloads"></a>
|
|
13
|
+
<a href="https://github.com/serradura/okf-gem/pkgs/container/okf"><img src="https://img.shields.io/badge/ghcr.io-okf-2496ED?logo=docker&logoColor=white" alt="Docker image"></a>
|
|
13
14
|
<a href="https://github.com/serradura/okf-gem/actions/workflows/main.yml"><img src="https://github.com/serradura/okf-gem/actions/workflows/main.yml/badge.svg" alt="CI"></a>
|
|
14
15
|
<a href="https://github.com/serradura/okf-gem"><img src="https://img.shields.io/badge/ruby-%3E%3D%202.4-black" alt="Ruby >= 2.4"></a>
|
|
15
16
|
<a href="LICENSE.txt"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License: Apache-2.0"></a>
|
|
@@ -21,18 +22,20 @@
|
|
|
21
22
|
<b><a href="https://okfgem.com">Site</a></b> ·
|
|
22
23
|
<b><a href="https://okfgem.com/docs/">Docs</a></b> ·
|
|
23
24
|
<b><a href="https://demo.okfgem.com">Live demo</a></b> ·
|
|
24
|
-
<b><a href="https://claude.okfgem.com">Claude plugin</a></b>
|
|
25
|
+
<b><a href="https://claude.okfgem.com">Claude plugin</a></b> ·
|
|
26
|
+
<b><a href="https://docker.okfgem.com">Docker image</a></b>
|
|
25
27
|
</p>
|
|
26
28
|
|
|
27
|
-
**okf-gem** (`okf` on RubyGems) is the complete
|
|
28
|
-
**Open Knowledge Format (OKF)** v0.1 bundles
|
|
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
32
|
|
|
30
33
|
The package, end to end:
|
|
31
34
|
|
|
32
35
|
<p align="center">
|
|
33
36
|
<picture>
|
|
34
37
|
<source media="(prefers-color-scheme: dark)" srcset=".github/overview-dark.png">
|
|
35
|
-
<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
|
|
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 three modes: okf server (a live local server), okf render (the same page exported as one static, self-contained HTML file you can host anywhere), and OKF::Server::App (the Rack app mounted in a Rails route). One gem, 100% local, Ruby 2.4 or newer, only rack and webrick as dependencies.">
|
|
36
39
|
</picture>
|
|
37
40
|
</p>
|
|
38
41
|
|
|
@@ -41,13 +44,13 @@ command-line tool (the library API is also usable in-process). Each capability
|
|
|
41
44
|
below links to the concept that documents it: this gem's own knowledge is an OKF
|
|
42
45
|
bundle, so you can read its design in the format it defends.
|
|
43
46
|
|
|
44
|
-
| Capability
|
|
45
|
-
|
|
|
46
|
-
| [Companion agent skill](.okf/capabilities/agent-skill.md)
|
|
47
|
-
| [Conformance validator](.okf/capabilities/validator.md)
|
|
48
|
-
| [Curation linter](.okf/capabilities/linter.md)
|
|
49
|
-
| [Interactive graph
|
|
50
|
-
| [Library API](.okf/capabilities/library-api.md)
|
|
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
|
+
| [Interactive graph](.okf/capabilities/graph-server.md) | Explore it — live or static? | `server` / `render` |
|
|
53
|
+
| [Library API](.okf/capabilities/library-api.md) | Can my Ruby program use it? | in-process |
|
|
51
54
|
|
|
52
55
|
> [!TIP]
|
|
53
56
|
> **Browse the gem as knowledge, not just docs.** This README is the front door;
|
|
@@ -70,6 +73,44 @@ It is deliberately light so it runs on the Ruby your OS already ships:
|
|
|
70
73
|
That range is not aspirational: CI runs the full test suite and RuboCop on every
|
|
71
74
|
one of these on each push.
|
|
72
75
|
|
|
76
|
+
## Try it in four steps
|
|
77
|
+
|
|
78
|
+
From zero to your first bundle.
|
|
79
|
+
|
|
80
|
+
**1. Get the `okf` command.** Two ways in; either one puts `okf` on your `PATH`.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
gem install okf # with Ruby
|
|
84
|
+
curl -fsSL https://docker.okfgem.com/install.sh | sh # no Ruby? Docker
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**2. Install the skill.** Teach your agent the format — Claude Code, or any
|
|
88
|
+
other agent.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
okf skill .claude # or: okf skill .agents
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**3. Start an agent session** where your project lives.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
claude
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**4. Make your first bundle.** Two ways in, by what you already have: docs keep
|
|
101
|
+
every word, code gets written up for you.
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
/okf migrate <path-to-your-docs> # have docs? adopted in place, bodies verbatim
|
|
105
|
+
/okf produce based on <path-to-your-code> # only code? the skill authors the concepts
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> [!TIP]
|
|
109
|
+
> **Once you have a bundle**, run `/okf maintain` in the agent session to keep it
|
|
110
|
+
> in sync as the code changes, and `okf server <folder>` to explore it as a graph.
|
|
111
|
+
> In Claude Code, the [plugin](#claude-code-plugin) adds a post-edit curation hook
|
|
112
|
+
> that runs `validate` + `lint` for you.
|
|
113
|
+
|
|
73
114
|
## Why OKF
|
|
74
115
|
|
|
75
116
|
Project knowledge (why a service exists, what a metric really measures, the
|
|
@@ -85,18 +126,21 @@ work with it.
|
|
|
85
126
|
Knowledge already has several homes near an agent, and each holds a different
|
|
86
127
|
thing. None of the others is built for curated, durable team knowledge:
|
|
87
128
|
|
|
88
|
-
| | OKF bundle (this)
|
|
89
|
-
| ------------------------------ |
|
|
90
|
-
| Holds | curated team knowledge
|
|
91
|
-
| Versioned with the code | ✅
|
|
92
|
-
| Portable across agents | ✅ plain Markdown + YAML
|
|
93
|
-
| Typed and queryable | ✅ frontmatter + graph
|
|
94
|
-
| Reviewed in PRs | ✅
|
|
95
|
-
| Scales past one context window | ✅ progressive disclosure
|
|
96
|
-
| Checked by tooling | ✅ (`okf validate` + `lint`)
|
|
97
|
-
|
|
98
|
-
The last
|
|
99
|
-
|
|
129
|
+
| | OKF bundle (this) | `CLAUDE.md` / `AGENTS.md` | Agent auto-memory | Wiki / Notion |
|
|
130
|
+
| ------------------------------ | ----------------------------------------------------- | -------------------------- | ------------------------ | ---------------- |
|
|
131
|
+
| Holds | curated team knowledge | standing instructions | what one agent picked up | human docs |
|
|
132
|
+
| Versioned with the code | ✅ | ✅ | ❌ | ❌ |
|
|
133
|
+
| Portable across agents | ✅ plain Markdown + YAML | ⚠️ per-harness conventions | ❌ per-agent store | ⚠️ export needed |
|
|
134
|
+
| Typed and queryable | ✅ frontmatter + graph | ❌ prose | ❌ | ⚠️ partially |
|
|
135
|
+
| Reviewed in PRs | ✅ | ✅ | ❌ implicit | ⚠️ rarely |
|
|
136
|
+
| Scales past one context window | ✅ progressive disclosure<br>(`okf index` + `search`) | ❌ loaded whole | ⚠️ partially | n/a |
|
|
137
|
+
| Checked by tooling | ✅ exit codes for CI<br>(`okf validate` + `lint`) | ❌ | ❌ | ❌ |
|
|
138
|
+
|
|
139
|
+
The last two rows are this gem's job. Scaling past one context window is
|
|
140
|
+
progressive disclosure — `okf index` reads the map, `okf search` pulls only the
|
|
141
|
+
concepts a task needs, so the bundle is never loaded whole. And drift never
|
|
142
|
+
hides here: the other homes have no detector, but `okf validate` and `lint` turn
|
|
143
|
+
a bundle's drift into findings you can gate on in CI.
|
|
100
144
|
|
|
101
145
|
## What a bundle looks like
|
|
102
146
|
|
|
@@ -158,6 +202,38 @@ putting the `okf` command on your `PATH`:
|
|
|
158
202
|
bundle exec rake install
|
|
159
203
|
```
|
|
160
204
|
|
|
205
|
+
### Run it with Docker (no Ruby needed)
|
|
206
|
+
|
|
207
|
+
Prefer not to install Ruby? The official image bundles the CLI, so every `okf`
|
|
208
|
+
command runs against a bundle you mount at `/data`:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# validate / lint / search / index … mirror the CLI, over the mounted bundle
|
|
212
|
+
docker run --rm -v "$PWD:/data" ghcr.io/serradura/okf validate .
|
|
213
|
+
|
|
214
|
+
# serve the live graph: bind 0.0.0.0 so the host can reach it, and publish the port
|
|
215
|
+
docker run --rm -v "$PWD:/data" -p 8808:8808 ghcr.io/serradura/okf server . --bind 0.0.0.0
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Then open <http://127.0.0.1:8808>. Images are published for `linux/amd64` and
|
|
219
|
+
`linux/arm64` on
|
|
220
|
+
[ghcr.io](https://github.com/serradura/okf-gem/pkgs/container/okf): `:latest`
|
|
221
|
+
tracks the newest release, or pin a version like `:1.5.0`.
|
|
222
|
+
|
|
223
|
+
Tired of the long line? Install a Docker-backed [`okf` command](https://docker.okfgem.com),
|
|
224
|
+
so every verb drops the `docker run` prefix and reads exactly like the native CLI
|
|
225
|
+
(mount, port, and bind handled for you). Do this only on a machine without the
|
|
226
|
+
gem:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
curl -fsSL https://docker.okfgem.com/install.sh | sh # or grab the script by hand
|
|
230
|
+
okf validate .
|
|
231
|
+
okf server .
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
On Windows the image runs under Docker Desktop (WSL2); install with PowerShell
|
|
235
|
+
instead: `irm https://docker.okfgem.com/install.ps1 | iex`.
|
|
236
|
+
|
|
161
237
|
## Command line
|
|
162
238
|
|
|
163
239
|
```bash
|
|
@@ -167,6 +243,7 @@ okf loose <dir> [--json] # list files with no gra
|
|
|
167
243
|
okf search <dir> <term…> [-e] [--in a,b] [...] # ranked text retrieval across metadata + bodies
|
|
168
244
|
okf index <dir> [--json] [--area A] [--no-body] # progressive-disclosure map (§6): bodies, rollups, listings
|
|
169
245
|
okf server <dir> [-p PORT] [--bind ADDR] [...] # serve the interactive graph over HTTP
|
|
246
|
+
okf render <dir> [-o FILE] [--layout NAME] [...] # export the graph as one static, self-contained HTML file
|
|
170
247
|
okf graph <dir> [--json] [--minimal] [--no-body] # print the knowledge graph
|
|
171
248
|
okf catalog | files | tags | types | stats <dir> [--json] # the browser views, on the CLI
|
|
172
249
|
okf skill <dest> [--here] [--force] # install the companion agent skill
|
|
@@ -186,6 +263,8 @@ OKF v0.1 conformance — docs
|
|
|
186
263
|
|
|
187
264
|
$ okf server docs
|
|
188
265
|
serving 37 concepts at http://127.0.0.1:8808 (Ctrl-C to stop)
|
|
266
|
+
|
|
267
|
+
$ okf render docs > public/index.html # the same page, static — host it anywhere
|
|
189
268
|
```
|
|
190
269
|
|
|
191
270
|
<picture>
|
|
@@ -197,6 +276,10 @@ _The graph server on this repo's own [`.okf`](.okf) bundle, with the
|
|
|
197
276
|
`capabilities/graph-server` concept selected. Try it live at
|
|
198
277
|
**[demo.okfgem.com](https://demo.okfgem.com)**._
|
|
199
278
|
|
|
279
|
+
To skip the server entirely, **`okf render <dir>`** writes that same page as one
|
|
280
|
+
self-contained HTML file, the whole bundle baked in, so you can publish the
|
|
281
|
+
graph on GitHub Pages or any static host.
|
|
282
|
+
|
|
200
283
|
`graph` and `server` are best-effort (§9): a file with invalid frontmatter is
|
|
201
284
|
skipped (and noted on stderr), not fatal, so one bad file never breaks the rest.
|
|
202
285
|
The [graph server](.okf/capabilities/graph-server.md) concept walks the request
|
|
@@ -247,15 +330,16 @@ skill plus a post-edit curation hook.
|
|
|
247
330
|
The skill routes a small set of verbs. In Claude Code they run as `/okf:gem
|
|
248
331
|
<verb>`; used standalone, the skill infers the verb from your request.
|
|
249
332
|
|
|
250
|
-
| Verb | What it does
|
|
251
|
-
| ---------------- |
|
|
252
|
-
| _(none)_ | Orient on the bundle and recommend the highest-value next move
|
|
253
|
-
| `search` | Answer a question from the bundle, token-lean: the map, the finder, only the winning bodies
|
|
254
|
-
| `produce` | Create or extend a bundle from code, docs, or knowledge in people's heads
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
333
|
+
| Verb | What it does |
|
|
334
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
335
|
+
| _(none)_ | Orient on the bundle and recommend the highest-value next move |
|
|
336
|
+
| `search` | Answer a question from the bundle, token-lean: the map, the finder, only the winning bodies |
|
|
337
|
+
| `produce` | Create or extend a bundle from code, docs, or knowledge in people's heads |
|
|
338
|
+
| `migrate` | Adopt existing Markdown docs in place: frontmatter and reserved files added, bodies kept verbatim |
|
|
339
|
+
| `maintain` | Sync the bundle's content with reality after the code or docs change |
|
|
340
|
+
| `consume` | Use the bundle as context for a task, writing back what you learn |
|
|
341
|
+
| `curate` | Structural upkeep as it stands: `validate` + `lint` + `loose` |
|
|
342
|
+
| `doctor` | Install and verify the CLI, then doctor the bundle |
|
|
259
343
|
| `<okf-cli-verb>` | Run any CLI verb (`validate`, `lint`, `search`, `index`, `server`, the read views) and interpret its output |
|
|
260
344
|
|
|
261
345
|
Point it at your agent's config directory (or its skills directory) and the tree
|
|
@@ -410,7 +494,7 @@ The plugin carries three pieces:
|
|
|
410
494
|
| Piece | What it does |
|
|
411
495
|
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
412
496
|
| `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`). |
|
|
413
|
-
| `/okf:gem` | The front door
|
|
497
|
+
| `/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). |
|
|
414
498
|
| 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. |
|
|
415
499
|
|
|
416
500
|
The hook stays silent outside bundles, and when the CLI is missing it suggests
|
data/lib/okf/cli.rb
CHANGED
|
@@ -55,6 +55,7 @@ module OKF
|
|
|
55
55
|
when "types" then types(argv)
|
|
56
56
|
when "stats" then stats(argv)
|
|
57
57
|
when "server" then server(argv)
|
|
58
|
+
when "render" then render(argv)
|
|
58
59
|
when "skill" then skill(argv)
|
|
59
60
|
when "version", "--version", "-v" then @out.puts(OKF::VERSION); 0
|
|
60
61
|
when "help", "--help", "-h" then usage(@out); 0
|
|
@@ -193,6 +194,7 @@ module OKF
|
|
|
193
194
|
|
|
194
195
|
def server(argv)
|
|
195
196
|
require "okf/server/app"
|
|
197
|
+
require "rack/deflater"
|
|
196
198
|
|
|
197
199
|
options = { port: 8808, bind: "127.0.0.1", title: nil, link: nil, layout: "cose" }
|
|
198
200
|
parser = OptionParser.new do |o|
|
|
@@ -215,10 +217,39 @@ module OKF
|
|
|
215
217
|
# tests drive this without a socket).
|
|
216
218
|
def run_server(folder, options)
|
|
217
219
|
app = OKF::Server::App.new(folder, title: options[:title] || folder.name, link: options[:link], layout: options[:layout])
|
|
220
|
+
app = Rack::Deflater.new(app) # gzip responses when the client accepts it — transparent, no new dependency
|
|
218
221
|
@out.puts "serving #{folder.graph.nodes.size} concepts at http://#{options[:bind]}:#{options[:port]} (Ctrl-C to stop)"
|
|
219
222
|
@runner.call(app, options[:bind], options[:port])
|
|
220
223
|
end
|
|
221
224
|
|
|
225
|
+
# The static counterpart to `server`: bake the whole bundle into one
|
|
226
|
+
# self-contained HTML file (bodies, catalog, index, logs baked in, no server
|
|
227
|
+
# needed — e.g. hosting on GitHub Pages). Prints to stdout unless -o is given.
|
|
228
|
+
def render(argv)
|
|
229
|
+
require "okf/server/app"
|
|
230
|
+
|
|
231
|
+
options = { output: nil, title: nil, link: nil, layout: "cose" }
|
|
232
|
+
parser = OptionParser.new do |o|
|
|
233
|
+
o.banner = "Usage: okf render <bundle-dir> [-o FILE] [--layout NAME] [-t title] [-l url]"
|
|
234
|
+
o.on("-o", "--output FILE", "write to FILE instead of stdout") { |v| options[:output] = v }
|
|
235
|
+
o.on("-t", "--title TITLE", "graph title (default: parent/bundle dir name)") { |v| options[:title] = v }
|
|
236
|
+
o.on("-l", "--link URL", "source URL shown in the header") { |v| options[:link] = v }
|
|
237
|
+
o.on("--layout NAME", OKF::Server::Graph::LAYOUTS, "initial layout (#{OKF::Server::Graph::LAYOUTS.join(", ")})") { |v| options[:layout] = v }
|
|
238
|
+
end
|
|
239
|
+
dir = positional_dir(parser, argv) or return 2
|
|
240
|
+
|
|
241
|
+
folder = OKF::Bundle::Folder.load(dir)
|
|
242
|
+
report_skipped(folder)
|
|
243
|
+
html = OKF::Server::App.new(folder, title: options[:title] || folder.name, link: options[:link], layout: options[:layout]).render_static
|
|
244
|
+
if options[:output]
|
|
245
|
+
File.write(options[:output], html)
|
|
246
|
+
@out.puts "wrote #{folder.graph(minimal: true).nodes.size} concepts to #{options[:output]}"
|
|
247
|
+
else
|
|
248
|
+
@out.print html
|
|
249
|
+
end
|
|
250
|
+
0
|
|
251
|
+
end
|
|
252
|
+
|
|
222
253
|
def graph(argv)
|
|
223
254
|
options = { json: false, minimal: false, body: true }
|
|
224
255
|
parser = OptionParser.new do |o|
|
|
@@ -947,6 +978,7 @@ module OKF
|
|
|
947
978
|
|
|
948
979
|
skill <dest> [--here] [--force] install the companion agent skill
|
|
949
980
|
server <dir> [-p PORT] [--bind ADDR] [...] serve an interactive HTML graph
|
|
981
|
+
render <dir> [-o FILE] [--layout NAME] [...] write a static, self-contained HTML graph
|
|
950
982
|
|
|
951
983
|
lint <dir> [--json] [--fail-on warn] [...] report curation-quality issues
|
|
952
984
|
loose <dir> [--json] list files with no graph links, by folder
|
data/lib/okf/server/app.rb
CHANGED
|
@@ -55,6 +55,13 @@ module OKF
|
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# The same interactive page, but with the whole bundle baked in — bodies,
|
|
59
|
+
# catalog, index and logs — so it needs no server. This is what `okf render`
|
|
60
|
+
# writes: the fetch getters resolve from the embedded payload, not from here.
|
|
61
|
+
def render_static
|
|
62
|
+
Graph.new(graph, title: @title || @folder.name, link: @link, layout: @layout, embed: embed_payload).render
|
|
63
|
+
end
|
|
64
|
+
|
|
58
65
|
private
|
|
59
66
|
|
|
60
67
|
# The minimal graph snapshot taken at boot — drives the page and the indexes.
|
|
@@ -96,6 +103,29 @@ module OKF
|
|
|
96
103
|
@page ||= Graph.new(graph, title: @title || @folder.name, link: @link, layout: @layout).render
|
|
97
104
|
end
|
|
98
105
|
|
|
106
|
+
# Everything the on-demand endpoints would serve, baked for render mode. The
|
|
107
|
+
# arrays match what each client getter extracts from the JSON envelope; the
|
|
108
|
+
# per-concept maps mirror /node (raw, unstripped body) and /node/meta (the
|
|
109
|
+
# same escaped fragment). Read from the in-memory bundle — no live disk read,
|
|
110
|
+
# since a static file is a snapshot, not a window on edits.
|
|
111
|
+
def embed_payload
|
|
112
|
+
{
|
|
113
|
+
catalog: @folder.catalog,
|
|
114
|
+
index: @folder.directory_index,
|
|
115
|
+
logs: logs[:logs],
|
|
116
|
+
bodies: bodies,
|
|
117
|
+
meta: meta
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def bodies
|
|
122
|
+
@folder.bundle.concepts.each_with_object({}) { |concept, map| map[concept.id] = concept.body.to_s }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def meta
|
|
126
|
+
@folder.bundle.concepts.each_with_object({}) { |concept, map| map[concept.id] = description_fragment(concept) }
|
|
127
|
+
end
|
|
128
|
+
|
|
99
129
|
def node_body(id)
|
|
100
130
|
concept = concept_for(id)
|
|
101
131
|
return not_found if concept.nil?
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<meta property="og:site_name" content="OKF">
|
|
9
9
|
<meta property="og:title" content="<%= og_title %>">
|
|
10
10
|
<meta property="og:description" content="<%= og_desc %>">
|
|
11
|
-
<meta property="og:image" content="https://okfgem.com/og-demo-
|
|
11
|
+
<meta property="og:image" content="https://okfgem.com/og-demo-v3.png">
|
|
12
12
|
<meta property="og:image:type" content="image/png">
|
|
13
13
|
<meta property="og:image:width" content="1200">
|
|
14
14
|
<meta property="og:image:height" content="630">
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<meta name="twitter:card" content="summary_large_image">
|
|
17
17
|
<meta name="twitter:title" content="<%= og_title %>">
|
|
18
18
|
<meta name="twitter:description" content="<%= og_desc %>">
|
|
19
|
-
<meta name="twitter:image" content="https://okfgem.com/og-demo-
|
|
19
|
+
<meta name="twitter:image" content="https://okfgem.com/og-demo-v3.png">
|
|
20
20
|
<meta name="twitter:image:alt" content="An interactive Open Knowledge Format knowledge graph.">
|
|
21
21
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='24' fill='%231a1a1a'/><polygon points='38,44 62,44 50,82' fill='%237a0a1e'/><polygon points='18,44 38,44 50,82' fill='%23a8112c'/><polygon points='62,44 82,44 50,82' fill='%23a8112c'/><polygon points='35,28 18,44 38,44' fill='%23dc1e3c'/><polygon points='65,28 82,44 62,44' fill='%23dc1e3c'/><polygon points='35,28 65,28 62,44 38,44' fill='%23f43f5e'/><polygon points='36,29 49,29 42,42' fill='%23fff' opacity='.38'/><polygon points='35,28 65,28 82,44 50,82 18,44' fill='none' stroke='%23ff6b7f' stroke-width='2' stroke-linejoin='round'/></svg>">
|
|
22
22
|
<script>/* Resolve theme before first paint so there is no flash. */
|
|
@@ -496,6 +496,11 @@
|
|
|
496
496
|
const NODES=<%= nodes_json %>, EDGES=<%= edges_json %>, TYPES=<%= types_json %>, TAGS=<%= tags_json %>;
|
|
497
497
|
const NODE_ENDPOINT=<%= @node_endpoint.to_json %>, META_ENDPOINT=<%= @meta_endpoint.to_json %>;
|
|
498
498
|
const CATALOG_ENDPOINT="catalog",INDEX_ENDPOINT="index",LOG_ENDPOINT="log";
|
|
499
|
+
/* null when served live (`okf server`) — the getters below fetch the endpoints
|
|
500
|
+
above. `okf render` injects a payload {catalog,index,logs,bodies,meta}, and
|
|
501
|
+
every getter resolves from it instead, so one file needs no server and the
|
|
502
|
+
endpoint consts above go inert. */
|
|
503
|
+
const EMBED=<%= embed_json %>;
|
|
499
504
|
const MIN=<%= OKF::Server::Graph::MIN_SIZE %>, MAX=<%= OKF::Server::Graph::MAX_SIZE %>;
|
|
500
505
|
const PALETTE=["#6E56CF","#D97757","#22C55E","#3B82F6","#EAB308","#EC4899","#14B8A6","#F97316","#A855F7","#0EA5E9","#84CC16","#EF4444","#64748B"];
|
|
501
506
|
const root=document.documentElement;
|
|
@@ -701,6 +706,12 @@ function showLog(path){LOGS=null;getLogs().then(logs=>{const l=logs.find(x=>x.pa
|
|
|
701
706
|
sideBody.innerHTML=`<span class="type">update log · §7</span><h2 class="title">${esc(path)}</h2><div class="body" id="dir-body"></div>`;
|
|
702
707
|
renderMarkdown(document.getElementById('dir-body'),l.content||'');
|
|
703
708
|
if(opened)requestAnimationFrame(()=>cy.resize());});}
|
|
709
|
+
/* Two per-concept reads shared by the inspector and the files preview: the raw
|
|
710
|
+
markdown body (rendered client-side) and the description fragment. Live they
|
|
711
|
+
hit /node and /node/meta; under EMBED they resolve from the baked payload.
|
|
712
|
+
Never memoized in server mode, so on-disk edits keep reflecting. */
|
|
713
|
+
function getNodeBody(id){return EMBED?Promise.resolve(EMBED.bodies[id]||''):fetch(NODE_ENDPOINT+'?id='+encodeURIComponent(id)).then(r=>r.ok?r.text():'');}
|
|
714
|
+
function getNodeMeta(id){return EMBED?Promise.resolve(EMBED.meta[id]||''):fetch(META_ENDPOINT+'?id='+encodeURIComponent(id)).then(r=>r.ok?r.text():'');}
|
|
704
715
|
function show(id){const n=byId[id];if(!n)return false;const ty=typeOf[id]||'Untyped';const c=color[ty]||'#64748b';
|
|
705
716
|
shownId=id;const opened=openPanel();sideBody.scrollTop=0;
|
|
706
717
|
sideBody.innerHTML=`<span class="type"><span class="dot" style="background:${c}"></span>${esc(ty)}</span>
|
|
@@ -708,10 +719,9 @@ function show(id){const n=byId[id];if(!n)return false;const ty=typeOf[id]||'Unty
|
|
|
708
719
|
<div class="tags">${(tagsOf[id]||[]).map(t=>`<span class="tag">${esc(t)}</span>`).join('')}</div>
|
|
709
720
|
${relList('Links to',outL[id])}${relList('Linked from',inL[id])}<div class="body" id="body"></div>`;
|
|
710
721
|
sideBody.querySelectorAll('[data-go]').forEach(a=>a.onclick=()=>select(a.getAttribute('data-go')));
|
|
711
|
-
const
|
|
712
|
-
fetch(META_ENDPOINT+'?id='+eid).then(r=>r.ok?r.text():'').then(h=>{const d=document.getElementById('desc');if(d){d.innerHTML=h||'<span class="empty">no description</span>';d.classList.remove('loading');}}).catch(()=>{});
|
|
722
|
+
getNodeMeta(id).then(h=>{const d=document.getElementById('desc');if(d){d.innerHTML=h||'<span class="empty">no description</span>';d.classList.remove('loading');}}).catch(()=>{});
|
|
713
723
|
const bodyEl=sideBody.querySelector('#body');
|
|
714
|
-
|
|
724
|
+
getNodeBody(id).then(md=>{if(bodyEl.isConnected)renderMarkdown(bodyEl,md);}).catch(()=>{});
|
|
715
725
|
return opened;}
|
|
716
726
|
/* One camera move per selection. When the click also opens the panel, the
|
|
717
727
|
container is still animating (--side-w, .22s), so pan now and the debounced
|
|
@@ -889,7 +899,7 @@ searchInput.oninput=()=>{q[view]=searchInput.value;
|
|
|
889
899
|
|
|
890
900
|
/* ── catalog ── */
|
|
891
901
|
let CATALOG=null;
|
|
892
|
-
function getCatalog(){return CATALOG||(CATALOG=fetch(CATALOG_ENDPOINT).then(r=>r.json()).then(d=>d.concepts));}
|
|
902
|
+
function getCatalog(){return CATALOG||(CATALOG=EMBED?Promise.resolve(EMBED.catalog):fetch(CATALOG_ENDPOINT).then(r=>r.json()).then(d=>d.concepts));}
|
|
893
903
|
const catActiveTypes=new Set(), catActiveAreas=new Set(), catActiveTags=new Set();
|
|
894
904
|
const catByCount=types.slice().sort((a,b)=>(TYPES[b]||[]).length-(TYPES[a]||[]).length);
|
|
895
905
|
const catChip=t=>`<span class="chip" data-t="${esc(t)}"><span class="dot" style="background:${color[t]}"></span>${esc(t)} <span class="c">${(TYPES[t]||[]).length}</span></span>`;
|
|
@@ -1024,7 +1034,7 @@ function openFile(id,list){fileSel=id;const c=(list||[]).find(x=>x.id===id)||byI
|
|
|
1024
1034
|
document.getElementById('fp-graph').hidden=false;
|
|
1025
1035
|
document.getElementById('fp-graph').onclick=()=>goToGraph(id);
|
|
1026
1036
|
const body=document.getElementById('fp-body');body.innerHTML='<p class="loading">loading…</p>';
|
|
1027
|
-
|
|
1037
|
+
getNodeBody(id).then(txt=>{
|
|
1028
1038
|
const wrap=document.createElement('div');wrap.className='body';renderMarkdown(wrap,txt);body.innerHTML='';body.appendChild(wrap);}).catch(()=>{body.innerHTML='<p class="empty">could not load this file</p>';});}
|
|
1029
1039
|
/* Reserved files read in the preview too: the index map from its payload, the
|
|
1030
1040
|
log fetched fresh on every click so a just-appended entry shows. */
|
|
@@ -1119,13 +1129,13 @@ document.addEventListener('fullscreenchange',()=>btnFull.setAttribute('aria-pres
|
|
|
1119
1129
|
|
|
1120
1130
|
/* ── the §6 map payload — feeds folder clicks, the Indexes tab, and previews ── */
|
|
1121
1131
|
let INDEX=null;
|
|
1122
|
-
function getIndex(){return INDEX||(INDEX=fetch(INDEX_ENDPOINT).then(r=>r.json()).then(d=>d.directories));}
|
|
1132
|
+
function getIndex(){return INDEX||(INDEX=EMBED?Promise.resolve(EMBED.index):fetch(INDEX_ENDPOINT).then(r=>r.json()).then(d=>d.directories));}
|
|
1123
1133
|
function listingHtml(items){if(!items||!items.length)return '<p class="empty" style="margin:10px 0 0">No concepts directly here.</p>';
|
|
1124
1134
|
return '<ul class="ix-listing">'+items.map(it=>`<li><a data-go="${esc(it.id)}">${esc(it.title||it.id)}</a>${it.description?` <span class="d">— ${esc(it.description)}</span>`:''}</li>`).join('')+'</ul>';}
|
|
1125
1135
|
|
|
1126
1136
|
/* ── log payload (the §7 history) — read by the Files view's log entries ── */
|
|
1127
1137
|
let LOGS=null;
|
|
1128
|
-
function getLogs(){return LOGS||(LOGS=fetch(LOG_ENDPOINT).then(r=>r.json()).then(d=>d.logs));}
|
|
1138
|
+
function getLogs(){return LOGS||(LOGS=EMBED?Promise.resolve(EMBED.logs):fetch(LOG_ENDPOINT).then(r=>r.json()).then(d=>d.logs));}
|
|
1129
1139
|
|
|
1130
1140
|
/* ── keyboard ── */
|
|
1131
1141
|
const VIEW_KEYS={'1':'graph','3':'files','4':'catalog','5':'tags','6':'stats'};
|
data/lib/okf/server/graph.rb
CHANGED
|
@@ -7,15 +7,23 @@ module OKF
|
|
|
7
7
|
# returns the HTML string.
|
|
8
8
|
#
|
|
9
9
|
# The page boots from a *minimal* payload — nodes carry only id + title, plus
|
|
10
|
-
# compact TYPES/TAGS inverted indexes for colouring and filtering
|
|
11
|
-
#
|
|
12
|
-
# fetch, rendering it client-side with marked. Node bodies are therefore NOT
|
|
13
|
-
# embedded here.
|
|
10
|
+
# compact TYPES/TAGS inverted indexes for colouring and filtering. It has two
|
|
11
|
+
# data modes, both driven by one template:
|
|
14
12
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# (
|
|
13
|
+
# server mode (embed: nil) — the default. Each concept's markdown body,
|
|
14
|
+
# metadata, catalog, index and log are pulled from OKF::Server::App on
|
|
15
|
+
# demand via fetch, so the initial payload stays small and bodies read
|
|
16
|
+
# live from disk (edits show without a restart). Nothing extra embedded.
|
|
17
|
+
# render mode (embed: payload) — `okf render` bakes the whole bundle in:
|
|
18
|
+
# the same fetch getters resolve from the injected payload instead, so
|
|
19
|
+
# the single file needs no server (e.g. hosting on GitHub Pages).
|
|
20
|
+
#
|
|
21
|
+
# NOTE (trust boundary): the page loads Cytoscape + marked from a CDN, so it
|
|
22
|
+
# needs network for those libraries even in render mode. Fetched/embedded
|
|
23
|
+
# markdown is sanitized client-side (DOMPurify.sanitize(marked.parse(...)))
|
|
24
|
+
# and all inline-<script> data — including any embedded body — is
|
|
25
|
+
# </script>-escaped by #json_for_script (stdlib ERB does not auto-escape).
|
|
26
|
+
# Still, only serve bundles you trust.
|
|
19
27
|
class Graph
|
|
20
28
|
TEMPLATE = File.expand_path("graph/template.html.erb", __dir__)
|
|
21
29
|
LAYOUTS = %w[cose concentric breadthfirst circle grid].freeze
|
|
@@ -31,13 +39,15 @@ module OKF
|
|
|
31
39
|
# +node_endpoint+/+meta_endpoint+ are the (mount-relative) URLs the page
|
|
32
40
|
# fetches a concept's raw markdown and metadata fragment from — relative so
|
|
33
41
|
# the page works whether served at "/" or mounted under a Rails prefix.
|
|
34
|
-
|
|
42
|
+
# +embed+ is the render-mode payload (nil = server mode); see the class doc.
|
|
43
|
+
def initialize(graph, title: nil, link: nil, layout: "cose", node_endpoint: "node", meta_endpoint: "node/meta", embed: nil)
|
|
35
44
|
@graph = graph
|
|
36
45
|
@title = title
|
|
37
46
|
@link = link
|
|
38
47
|
@layout = layout
|
|
39
48
|
@node_endpoint = node_endpoint
|
|
40
49
|
@meta_endpoint = meta_endpoint
|
|
50
|
+
@embed = embed
|
|
41
51
|
end
|
|
42
52
|
|
|
43
53
|
def render
|
|
@@ -86,6 +96,12 @@ module OKF
|
|
|
86
96
|
json_for_script(@graph.tag_index)
|
|
87
97
|
end
|
|
88
98
|
|
|
99
|
+
# The render-mode payload, or the literal `null` in server mode — both from
|
|
100
|
+
# the same </script>-escaping helper, so injection stays uniform and safe.
|
|
101
|
+
def embed_json
|
|
102
|
+
json_for_script(@embed)
|
|
103
|
+
end
|
|
104
|
+
|
|
89
105
|
# JSON-encode for safe embedding in an inline <script>: escaping every `<` to
|
|
90
106
|
# its JSON unicode escape neutralizes </script>, <!-- and <script in one
|
|
91
107
|
# stroke, and the result stays valid JSON *and* JavaScript.
|
data/lib/okf/skill/SKILL.md
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okf
|
|
3
3
|
description: >-
|
|
4
|
-
Be the expert on Open Knowledge Format (OKF) — portable knowledge as a
|
|
5
|
-
of markdown files with YAML frontmatter that
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
carrying `okf_version`. Triggers on: "document this in OKF", "update the
|
|
15
|
-
knowledge bundle", "capture this as a concept", "what do we know about X?",
|
|
16
|
-
"where is X documented?", "search the bundle", "validate/lint/serve the
|
|
17
|
-
bundle", or a task needing knowledge from an OKF bundle already in the repo.
|
|
4
|
+
Be the expert on Open Knowledge Format (OKF) — portable project knowledge as a
|
|
5
|
+
directory of markdown files with YAML frontmatter that humans and agents read
|
|
6
|
+
from one source. Use when capturing knowledge into a bundle (a service, schema,
|
|
7
|
+
metric, decision, runbook: "document this in OKF", "capture this as a concept"),
|
|
8
|
+
retrieving from one without reading it whole ("what do we know about X?", "where
|
|
9
|
+
is X documented?", "search the bundle"), updating one after code or docs change
|
|
10
|
+
("update the knowledge bundle"), checking its conformance or curation quality
|
|
11
|
+
("validate/lint the bundle"), serving or rendering it as a graph, or working in a
|
|
12
|
+
repo that already carries an OKF bundle — a `.okf/` directory or a root `index.md`
|
|
13
|
+
carrying `okf_version`.
|
|
18
14
|
user-invocable: true
|
|
19
15
|
argument-hint: "[search|produce|maintain|consume|<okf-cli-verb>] [dir] [--flags]"
|
|
20
16
|
allowed-tools: Read Write Edit Grep Glob Bash
|
|
@@ -120,7 +116,8 @@ read the modelling craft in [authoring.md](reference/authoring.md) before
|
|
|
120
116
|
producing or maintaining, and the verbatim spec [SPEC.md](reference/SPEC.md)
|
|
121
117
|
when you need chapter and verse.
|
|
122
118
|
|
|
123
|
-
**No subcommand?** Infer intent: "document this / capture X" → `produce`;
|
|
119
|
+
**No subcommand?** Infer intent: "document this / capture X" → `produce`;
|
|
120
|
+
"convert / migrate / OKFy these existing docs into a bundle" → `migrate`; "the
|
|
124
121
|
code changed, update the docs" → `maintain`; "what do we know about X / where
|
|
125
122
|
is X documented" → `search`; a repo already carrying a bundle plus a task
|
|
126
123
|
needing its knowledge → `consume`; "check / graph / preview it" → run the
|
|
@@ -130,6 +127,11 @@ matching CLI verb and interpret the result. When genuinely ambiguous, ask.
|
|
|
130
127
|
root, but first detect whether the project already keeps its bundle elsewhere
|
|
131
128
|
(e.g. `docs/`) and prefer that. Commit the bundle alongside the code it describes.
|
|
132
129
|
|
|
130
|
+
**Target isn't a bundle?** When a verb points at a directory that holds markdown
|
|
131
|
+
but no root `index.md` carrying `okf_version` — `validate` failing wholesale on
|
|
132
|
+
missing frontmatter — don't grind through the errors: suggest `migrate` (OKFy it
|
|
133
|
+
in place, bodies verbatim) and let the user pick.
|
|
134
|
+
|
|
133
135
|
## Commands
|
|
134
136
|
|
|
135
137
|
The first word of the arguments picks a row. **No arguments at all** — someone
|
|
@@ -143,11 +145,12 @@ Read the referenced playbook before executing — it *is* the procedure.
|
|
|
143
145
|
| *(none)* | Orient | recommend the highest-value next move; never auto-run | [playbooks/menu.md](playbooks/menu.md) |
|
|
144
146
|
| `search` | Use | answer a question from the bundle: map → finder → only the winning bodies | [playbooks/search.md](playbooks/search.md) |
|
|
145
147
|
| `produce` | Author | create or extend a bundle | [playbooks/produce.md](playbooks/produce.md) |
|
|
148
|
+
| `migrate` | Author | convert existing docs in place: frontmatter + reserved files, bodies verbatim | [playbooks/migrate.md](playbooks/migrate.md) |
|
|
146
149
|
| `maintain` | Author | sync the bundle's content with reality after a change | [playbooks/maintain.md](playbooks/maintain.md) |
|
|
147
150
|
| `consume` | Use | use the bundle as context for a task | [playbooks/consume.md](playbooks/consume.md) |
|
|
148
151
|
| `curate` | Curate | structural upkeep as it stands: validate + lint + loose | [playbooks/curate.md](playbooks/curate.md) |
|
|
149
152
|
| `doctor` | Setup | install and verify the CLI, then doctor the bundle | [playbooks/doctor.md](playbooks/doctor.md) |
|
|
150
|
-
| `<okf-cli-verb>` | Read | validate, lint, loose, index, catalog, files, tags, types, stats, graph, server, skill | `okf <verb> --help` + [reference/cli.md](reference/cli.md) |
|
|
153
|
+
| `<okf-cli-verb>` | Read | validate, lint, loose, index, catalog, files, tags, types, stats, graph, server, render, skill | `okf <verb> --help` + [reference/cli.md](reference/cli.md) |
|
|
151
154
|
|
|
152
155
|
Two boundaries worth keeping sharp: `curate` is structural upkeep only — when
|
|
153
156
|
the *content* no longer matches reality, that is `maintain` — and `doctor` is
|
|
@@ -11,8 +11,11 @@ is the lede.
|
|
|
11
11
|
here. Everything below needs the CLI.
|
|
12
12
|
2. **Bundle present?** Locate one: the directory you were given, else a `.okf/`
|
|
13
13
|
directory or a root `index.md` whose frontmatter carries `okf_version`.
|
|
14
|
-
- **No bundle** →
|
|
15
|
-
|
|
14
|
+
- **No bundle** → when the target (or an obvious docs directory) already
|
|
15
|
+
holds markdown documentation, lead with **`migrate`** (OKFy it in place —
|
|
16
|
+
frontmatter on, bodies verbatim); otherwise lead with **`produce`**
|
|
17
|
+
(create the first bundle from the code, docs, or what lives only in
|
|
18
|
+
people's heads). Nothing else applies yet.
|
|
16
19
|
3. **Read the bundle's state** from the CLI, not by eyeballing:
|
|
17
20
|
`okf validate <root>`, `okf lint <root>`, `okf loose <root>` — the plain
|
|
18
21
|
text views, which are lighter than `--json` when you are reading a report
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Playbook: migrate — OKFy existing docs in place
|
|
2
|
+
|
|
3
|
+
Adopt documentation that already *is* the knowledge: frontmatter on, reserved
|
|
4
|
+
files in, every body kept **verbatim**. The output must be recognizably the
|
|
5
|
+
input — when the documents should instead be distilled into new concepts, that
|
|
6
|
+
is [produce.md](produce.md), not migrate. If the `okf` CLI is missing, stop and
|
|
7
|
+
follow [doctor.md](doctor.md) first.
|
|
8
|
+
|
|
9
|
+
Bodies are sacred: migrate never rewrites, reorders, or summarizes a body — the
|
|
10
|
+
one permitted edit besides prepending frontmatter is repointing a relative link
|
|
11
|
+
that a file move broke. <!-- rule:okf-migrate-verbatim -->
|
|
12
|
+
Read the modelling craft in [authoring.md](../reference/authoring.md) before a
|
|
13
|
+
non-trivial migration; its type and tag rules apply here unchanged.
|
|
14
|
+
|
|
15
|
+
1. **Inventory from the validator, not by eyeballing.** `okf validate <dir>
|
|
16
|
+
--json` enumerates every file missing frontmatter or `type` and every
|
|
17
|
+
malformed reserved file — that list is the worklist, and the pass is done
|
|
18
|
+
when it reports zero.
|
|
19
|
+
2. **Prepend frontmatter; do not touch the body below it.** Use the frontmatter
|
|
20
|
+
block of [templates/concept.md](../templates/concept.md): a small `type`
|
|
21
|
+
vocabulary derived from what the documents *are* (reuse before minting —
|
|
22
|
+
check `okf types <dir>` as you go), `title`/`description` from each
|
|
23
|
+
document's own heading and purpose line, `timestamp` from the document's own
|
|
24
|
+
date when it carries one, `tags` only where connective.
|
|
25
|
+
3. **Keep the directory topology** — it is already domain knowledge. Default
|
|
26
|
+
one file = one concept. When a file shows split signals (two `type`s
|
|
27
|
+
fighting for the frontmatter, two audiences), flag it for a later `curate`
|
|
28
|
+
pass; never split, rename, or restructure during migration.
|
|
29
|
+
4. **Reserved files.** A bundle-root `index.md` from
|
|
30
|
+
[templates/root-index.md](../templates/root-index.md) (frontmatter is
|
|
31
|
+
`okf_version: "0.1"` and nothing else), a nested `index.md` per directory
|
|
32
|
+
from [templates/index.md](../templates/index.md), and `log.md` with a dated
|
|
33
|
+
**Creation** entry naming where the documents came from.
|
|
34
|
+
5. **Links.** The documents' existing relative links become the graph's edges —
|
|
35
|
+
verify they resolve inside the bundle and repoint only what a move broke.
|
|
36
|
+
Links pointing outside the bundle are tolerated (§5.3); leave them.
|
|
37
|
+
6. **Close out** — walk the
|
|
38
|
+
[Closeout gate](../reference/authoring.md#closeout--the-finishing-gate):
|
|
39
|
+
`validate` zero errors, `lint` (pass `--stale-after` when you stamped
|
|
40
|
+
timestamps), `loose`, tag review, index eyeball. Then prove the promise:
|
|
41
|
+
each concept with its frontmatter block stripped is byte-identical to the
|
|
42
|
+
source document.
|
|
@@ -8,7 +8,8 @@ vocabulary, topology, links, citations — lives in
|
|
|
8
8
|
2. Pick the source(s): **code** (derive concepts from source, READMEs, docstrings,
|
|
9
9
|
config), **docs/wiki** (distill pages into concepts; cite the originals under
|
|
10
10
|
`# Citations`), **manual** (decisions, playbooks, metrics that live only in
|
|
11
|
-
people's heads).
|
|
11
|
+
people's heads). If the source documents should survive as the concepts
|
|
12
|
+
themselves — verbatim — that is [migrate.md](migrate.md), not produce.
|
|
12
13
|
3. Choose a domain-based directory layout. One concept per file.
|
|
13
14
|
4. Write each concept from [templates/concept.md](../templates/concept.md): a
|
|
14
15
|
descriptive `type` from the bundle's vocabulary, recommended fields filled,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# OKF tool verbs — the `okf` CLI
|
|
2
2
|
|
|
3
3
|
`validate`, `lint`, `loose`, `search`, `index`, `catalog`, `files`, `tags`, `types`,
|
|
4
|
-
`stats`, `server`, and `graph` are **not** eyeball passes and are not
|
|
4
|
+
`stats`, `server`, `render`, and `graph` are **not** eyeball passes and are not
|
|
5
5
|
reimplemented in this skill. They run the deterministic `okf` executable shipped by
|
|
6
6
|
the companion gem — the single source of truth for OKF mechanics. Your job is to
|
|
7
7
|
invoke it correctly and interpret the result, not to reason out conformance by hand.
|
|
@@ -39,7 +39,7 @@ difference between a few hundred bytes and hundreds of KB, since the per-item ro
|
|
|
39
39
|
`body`.
|
|
40
40
|
|
|
41
41
|
**Exit codes:** `0` success · `1` non-conformant bundle (or a `lint --fail-on`
|
|
42
|
-
threshold crossed) · `2` usage error. `graph` and `
|
|
42
|
+
threshold crossed) · `2` usage error. `graph`, `server`, and `render` are best-effort
|
|
43
43
|
(§9): a file with invalid frontmatter is skipped and noted on stderr, never fatal.
|
|
44
44
|
|
|
45
45
|
## validate — the hard gate (§9)
|
|
@@ -223,6 +223,23 @@ viewer libraries (Cytoscape, marked, DOMPurify — plus Mermaid and Panzoom,
|
|
|
223
223
|
lazy-loaded on first use) from a CDN and renders whatever
|
|
224
224
|
links the bundle carries — so only serve bundles you trust.
|
|
225
225
|
|
|
226
|
+
## render — static graph export
|
|
227
|
+
|
|
228
|
+
Writes the same interactive page as one static, self-contained HTML file
|
|
229
|
+
(`okf render <dir>`), so the graph hosts where there is no server — GitHub Pages,
|
|
230
|
+
an object store, an attachment. Prints to stdout (`okf render <dir> > graph.html`)
|
|
231
|
+
or writes `-o FILE`; `--title`/`--link`/`--layout` mirror `server`. It is the same
|
|
232
|
+
template `server` renders, one switch apart: rather than fetching each body,
|
|
233
|
+
description, catalog, index, and log live, `render` bakes the whole bundle into
|
|
234
|
+
the page and the browser reads from that embedded payload — no server, no build
|
|
235
|
+
step. The trade-off is weight (every body is inlined), so `server` stays the
|
|
236
|
+
choice for a bundle too large to ship whole.
|
|
237
|
+
|
|
238
|
+
**Trust boundary:** the same two guards as `server` — every inlined body is
|
|
239
|
+
`</script>`-escaped like the graph data and still sanitized by DOMPurify when
|
|
240
|
+
rendered — so a static file is no laxer than the live server. Only render bundles
|
|
241
|
+
you trust.
|
|
242
|
+
|
|
226
243
|
## graph — the raw structure
|
|
227
244
|
|
|
228
245
|
Prints the node/edge graph. `--json` emits a machine-readable dump (`nodes` with
|
data/lib/okf/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: okf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rodrigo Serradura
|
|
@@ -87,6 +87,7 @@ files:
|
|
|
87
87
|
- lib/okf/skill/playbooks/doctor.md
|
|
88
88
|
- lib/okf/skill/playbooks/maintain.md
|
|
89
89
|
- lib/okf/skill/playbooks/menu.md
|
|
90
|
+
- lib/okf/skill/playbooks/migrate.md
|
|
90
91
|
- lib/okf/skill/playbooks/produce.md
|
|
91
92
|
- lib/okf/skill/playbooks/search.md
|
|
92
93
|
- lib/okf/skill/reference/APACHE-2.0.txt
|
|
@@ -123,6 +124,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
123
124
|
requirements: []
|
|
124
125
|
rubygems_version: 4.0.16
|
|
125
126
|
specification_version: 4
|
|
126
|
-
summary: '
|
|
127
|
-
|
|
127
|
+
summary: 'The complete toolkit for the Open Knowledge Format: an agent skill, a CLI
|
|
128
|
+
and library, and a live knowledge graph. 100% local.'
|
|
128
129
|
test_files: []
|