okf-gem · blog
Release · 1.12.0

Bundles you can group, a graph you can read: okf 1.12.0

RS Rodrigo Serradura · ·6 min read
🇧🇷 Ler em português

New here: okf is a Ruby gem for the Open Knowledge Format, where a bundle is a directory of markdown files and each file is a concept, one idea carrying frontmatter and links to its neighbours (start here).

Two things in okf 1.12.0 change what a bundle set can be. The registry stops being a per-machine convenience and becomes something you commit next to your code, and a named group of bundles gets a single slug you can search, serve, and nest.

The third thing changes what the graph page hands you when the bundle is dense: a spine you can read, instead of every arrow at once.

A registry that lives in the repo

$ okf registry init

That creates a project-local .okf-registry.json in the current directory. From then on okf discovers it by walking up from wherever you are, and every registry operation and every @slug resolves through it instead of the global one in $OKF_HOME.

The practical effect: a bare okf server inside a repo serves that repo's bundles with no global setup on the machine. A teammate clones and the names already work.

That only holds if the paths travel, so they do. A bundle inside the registry's own tree is written relative to the .okf-registry.json, which means the file can be committed and a checkout on another machine, or a container that mounts it, resolves the same bundles unchanged. A bundle outside the tree keeps an absolute path, because it cannot travel and pretending otherwise would break on the first clone. Paths still read back absolute everywhere the CLI reports them; the relative form lives only on disk.

Worth knowing if you commit the file: an existing absolute local entry migrates to relative on its next write, so a .okf-registry.json you already had can rewrite its own paths the first time you touch it. The global $OKF_HOME registry is not affected and stores absolute paths as before.

Nested registries resolve nearest-first, okf registry list names the local file it found, and OKF_NO_DISCOVERY=1 forces the global one for a caller with a fixed working directory, such as CI.

Groups: one slug for a set of bundles

$ okf registry group backend @orders @billing @shared-vocab
$ okf search @backend "idempotency"

A group is a registry slug naming a list of members, and members can be bundles or other groups, so they nest. It resolves recursively and path-deduped down to its bundle leaves.

okf search @backend merges the members into one ranking rather than stapling per-bundle lists together, and okf server @backend mounts each of them, the first at /. Both skip a member whose directory has vanished with a note, exactly as @all already did.

Every single-bundle verb refuses a group and exits 2. It is the same rule that refuses a second bundle, through the same seam. ungroup removes members, emptying a group deletes it, and del and rename span a group slug too, so one rename cascades across every member list and one del drops the slug and deletes any group it empties.

The graph opens on its spine

A bundle of 28 concepts joined by 178 links is unreadable when you draw all of it, and the reason is the arrows rather than the dots. So links became a layer with three amounts: every link, the spine, or none.

The okf graph page with every link drawn. Thirty round concept nodes are buried under a dense web of grey arrows crossing the whole canvas, so no single path is legible. The toolbar's link-amount control is set to All links. The header reads okf, 30 concepts, 191 links. The okf graph page with every link drawn. Thirty round concept nodes are buried under a dense web of grey arrows crossing the whole canvas, so no single path is legible. The toolbar's link-amount control is set to All links. The header reads okf, 30 concepts, 191 links.
Every link at once: 191 edges over 30 concepts, unreadable because of the arrows, not the dots.

The spine is each concept's strongest edge, chosen so that it touches every linked concept and leaves nobody stranded. A dense bundle now opens there instead of greeting its reader with the thicket, and a selected concept always shows its own links in full.

The same graph in the same node positions, with only the spine drawn: each concept keeps its single strongest link, so the web thins to a readable shape that still reaches every node. The link-amount control is set to Spine. The header reads okf, 30 concepts, 191 links. The same graph in the same node positions, with only the spine drawn: each concept keeps its single strongest link, so the web thins to a readable shape that still reaches every node. The link-amount control is set to Spine. The header reads okf, 30 concepts, 191 links.
The same layout on its spine: each concept's strongest edge only, so the shape reads at a glance. This is where a dense bundle now opens.

The amount is a control you can move. In the graph toolbar there is a three-segment button group for how many links to draw, its tooltips reading "All links", "Spine", and "No links", and it switches at any time, so a graph that opened on its spine is one click from every arrow and one more from none. okf render --map and okf server --map start you at the far end of the same control: no links, directories boxed, a concept's own links appearing when you select it.

There is a speed dividend above 800 edges: the first layout runs on the spine alone and the rest arrive a frame later with no re-layout, so you reach something readable sooner and nothing on screen moves while the remainder lands.

graph --traffic: is this a concern or a container?

--hubs measures concepts. The judgement you actually make when tidying a bundle is about directories, and nothing measured anything at that grain.

okf graph --traffic collapses each concept into its directory and the links between two directories into one weighted arc, then reports internal, out, and in traffic per directory with a cohesion, its internal share of that total:

$ okf graph @okf-site --traffic

Traffic - @okf-site (8 dirs, 16 of 29 arcs at weight 2 or more)

  Dir         Concepts  Internal   Out    In  Cohesion
  runbooks           2         0    14     7        0%
  properties         4         1    14     9        4%
  upstream           2         1     7     7        7%
  social             2         2     3     7       17%
  pipeline           4         7     7    14       25%
  content            7         9    12    14       26%
  delivery           4         7     5     4       44%
  (root)             0         0     0     0         -

  Arcs
    runbooks   → content     ×6
    properties → content     ×5
    content    → pipeline    ×4
    content    → properties  ×4
    content    → runbooks    ×3
    pipeline   → content     ×3
    properties → delivery    ×3
    properties → pipeline    ×3
    [8 more arcs, down to weight 2]

Rows sort by cohesion ascending, so the directories with a case to answer come first, and the Arcs block under the table names the ones that survived the cut. The title's question, concern or container, is one the numbers answer row by row. runbooks sits at 0% cohesion with fourteen links out and none between its own two concepts: that is a container, files grouped by what they are for rather than a concern whose parts refer to each other. delivery is the opposite at 44%, the most self-contained directory here, its concepts mostly citing one another, which is what a real concern looks like. (root) holds no concepts and no traffic, so it prints a dash rather than a 0% it did not earn.

The arc cut is fitted to the bundle instead of fixed, because one threshold does not suit every tree: across the ten bundles the gem was measured against, weight 3 ranged from 2 arcs to 136. --cut N overrides it, and cohesion is computed over every arc regardless, so narrowing the picture never moves the evidence.

One rename, and one fix

The derived area field is now top_dir, across the catalog, search, stats, and graph --hubs. area was never the spec's word, which speaks only of dir, so the rollup now names itself in the spec's vocabulary: the dir at the top level.

If you parse --json, these are the keys that moved: catalog and search rows carry top_dir where they carried area, graph --hubs emits top_dir and by_top_dir, and stats emits top_dirs and by_top_dir. The deprecated --area and --by area input flags are unchanged, still warning and still mapping to --dir and --by dir. No behaviour changed and the hub numbers are identical.

Fixed: a project-local server now keeps its relative-path anchor across a re-open. Before, the Bundles panel drew every in-tree bundle as "folder is gone", and adding a bundle from the browser flattened it to an absolute path, undoing the portability relative storage exists for.

Getting it

$ gem update okf
$ cd your-project
$ okf registry init
$ okf registry set .okf --as your-bundle
$ okf graph @your-bundle --traffic

registry init creates an empty registry, so registry set is the step that puts a bundle in it; point it at any bundle directory and --as names the slug (it defaults to the directory's basename).

Upgrading is otherwise uneventful: every command and flag you already run still works, --area and --by area included, still warning exactly as before. The one thing that can break is a script reading the old area key out of --json, which is now top_dir.

The --depth cut the spine sits beside arrived in 1.11.0. Full detail is in the changelog.