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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48baf9115b68a210ae385dbddc1a9956dfd16f5424614629db9fd43ff7b40fc4
4
- data.tar.gz: 32824378ebd2856cf1272cc9bfd75b2d989ef87788f38759e794acf6c4594b99
3
+ metadata.gz: 9a83101e62e3b3bc97d7351580d50ecc017be391a5edbde2e8f52388de529111
4
+ data.tar.gz: 040be5295642da56cbeed342424872469d1c2b15efcb1b477f10076d7c90fafc
5
5
  SHA512:
6
- metadata.gz: 8add8ee3eb50b29bb7169544a27135d8221a54bcd2f1a5b98c221ec90a3ac1e836fa2da76456da889f7b396958835ac4ee39cf308a4ea9be88c47e2d65b9636c
7
- data.tar.gz: 8a84bfd874afff0cdf002f6fcff57df2042e6f04598d4ce41553bd3bde800e8645568008b5ad8491c89d1d0317477bd54c3cd7d6ab576dc2535fb722517fa43d
6
+ metadata.gz: 47fb84d8dffc04eee30eb65adfaca756ae43315e2d152e62692084a5bd04c6a7992529d243b2375412a8339a55db891c3ae18d679e171d5709e9426b93686047
7
+ data.tar.gz: 30390e1eecc1570b28531c374b21200309630aad87f821a7c3f25b631ca5285e03b91baab9048f69127931a09368da1ab38a089fc9bc25473c05da3c0fadfe2b
data/CHANGELOG.md CHANGED
@@ -5,6 +5,299 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.12.0] - 2026-07-24
9
+
10
+ ### Added
11
+
12
+ - **`okf graph --traffic`** — the link graph read one grain coarser. `--hubs`
13
+ measures concepts, but the refine playbook's directory judgements ("does this
14
+ directory prune? a concern, or a container?") had nothing measured at their
15
+ grain. `--traffic` collapses each concept into its directory and the links
16
+ between two directories into one weighted arc, so every row carries its
17
+ internal / out / in traffic and a **cohesion** — its internal share of that
18
+ total. That is cohesion versus coupling on a knowledge tree: near-zero cohesion
19
+ under heavy inbound is a shared vocabulary doing its job, heavy outbound with
20
+ nothing back is a projection wearing a directory. The rows sort by cohesion
21
+ ascending, so the directories with a case to answer come first, and a directory
22
+ with no traffic at all prints `—` rather than a `0%` it did not earn. The arc
23
+ **cut** is fitted to the bundle, not fixed — at weight 3, ten bundles ranged
24
+ from 2 arcs to 136 — and `--cut N` overrides it; cohesion is computed over
25
+ *every* arc regardless, so narrowing the drawn picture never moves the
26
+ evidence. JSON: `{ bundle, cut, fitted, dirs, arcs, total_arcs }`. The pure
27
+ model underneath is `Bundle::Skeleton` — concepts folded to directories, links
28
+ to weighted arcs, every edge tagged with the cut it survives (a local-degree
29
+ sparsifier, union rule, so no linked concept is ever stranded) — and it does no
30
+ I/O and draws nothing: it names a cut rather than taking one, so both the graph
31
+ page and the CLI narrow the same emission their own way.
32
+ - **The graph page draws links in three amounts, and opens dense on its spine.**
33
+ 227 links over 47 concepts at degree 9.7 is unreadable because of its arrows,
34
+ not its dots — so links become a layer: **every** link, the **spine** (each
35
+ concept's strongest edge — the Skeleton's `keep_at===0` set, chosen so it
36
+ touches every linked concept), or **none**, with a selected concept's own links
37
+ always shown in full. A dense bundle now opens on its spine rather than greeting
38
+ the reader with the thicket; the trigger is undirected degree above a floor set
39
+ between a tree's ~2 and the 9.7 that drove it, and `--map` overrides to none
40
+ with the directories boxed. Above 800 edges the first layout runs on the spine
41
+ alone and the rest arrive a frame later with no re-layout — **6.25 s → 3.29 s**
42
+ on 414 concepts, nothing on screen moving. Proven in both render modes on a
43
+ committed 110-concept / 880-link fixture, the only one over both floors.
44
+ - **Registry groups — a named, recursive set of bundles.** A group is a registry
45
+ slug that names a list of members (bundle *or* group slugs, so they nest) and
46
+ resolves recursively, path-deduped, to its bundle leaves. `okf registry group
47
+ <slug> <@member…>` creates one or adds to it, `ungroup` removes members (and
48
+ emptying a group deletes it), and `del`/`rename` now span a group slug too —
49
+ one name cascades across every member list, one `del` cascade-drops the slug and
50
+ deletes any group it empties. Groups live in their own list in the registry
51
+ JSON, so the first-is-default rule and the `File.directory?` guards never meet a
52
+ pathless entry. `okf search @backend` merges the members into one ranking and
53
+ `okf server @backend` mounts each (the first at `/`), both skipping a vanished
54
+ member with a note, exactly as `@all` does. Every single-bundle verb **refuses**
55
+ a `@group` (exit 2) — the same rule that refuses a second bundle, through the
56
+ same `resolve_registered` seam.
57
+ - **`okf registry init`** — create a project-local `.okf-registry.json` in the
58
+ current directory. Once it exists, okf discovers it by walking up from the
59
+ working directory, and every registry operation — and every `@slug` — resolves
60
+ through it in place of the global `$OKF_HOME` registry, so a bare `okf server`
61
+ inside a repo serves that repo's bundles with no global setup. The nearest one
62
+ on the path wins (nested registries resolve nearest-first), `okf registry list`
63
+ names the local file it found, and `OKF_NO_DISCOVERY=1` forces the global one —
64
+ the escape hatch for a fixed-cwd caller (CI, a tool).
65
+ - **A local registry stores portable, relative paths.** A bundle inside the
66
+ registry's own tree is written relative to the `.okf-registry.json`, so the file
67
+ can be committed and travels with the repo — a checkout on another machine, or a
68
+ container that mounts it, resolves the same bundles unchanged. A bundle outside
69
+ the tree keeps an absolute path (it cannot travel). Paths still read back
70
+ absolute everywhere the CLI reports them; the relative form lives only on disk,
71
+ and an existing absolute local entry migrates to relative on its next write. The
72
+ global `$OKF_HOME` registry is unchanged — it stores absolute paths as before.
73
+
74
+ ### Changed
75
+
76
+ - **The derived `area` field is renamed `top_dir`** — the first-path-segment
77
+ rollup the catalog, search, `stats`, and `graph --hubs` carry. `area` was never
78
+ the OKF spec's word (the spec speaks only of `dir`), so the rollup now names
79
+ itself in the spec's vocabulary: it is the `dir` at the top level. The `--json`
80
+ keys move with it — catalog/search rows carry `top_dir` in place of `area`,
81
+ `stats` emits `top_dirs`/`by_top_dir`, and `graph --hubs` emits
82
+ `top_dir`/`by_top_dir`. The **deprecated `--area`/`--by area` input flags are
83
+ unchanged** — they still warn and map to `--dir`/`--by dir`, and now source the
84
+ renamed field internally. No behavior changes; the well-homed-hub numbers are
85
+ identical.
86
+
87
+ ### Fixed
88
+
89
+ - **A local-registry server preserves its relative-path anchor across a
90
+ re-open.** The hub re-read its boot registry with `OKF::Registry.new(path)`,
91
+ which drops the `relative_base` a discovered `.okf-registry.json` carries — so
92
+ on a project-local server the Bundles panel matched each mounted bundle's
93
+ absolute root against the re-read entry's *relative* path, missed, and drew
94
+ every in-tree bundle as "folder is gone", while a browser add flattened the new
95
+ bundle to an absolute path, undoing the portability relative storage exists for.
96
+ `Registry#reopen` re-reads the same file anchored the same way, and both hub
97
+ re-open sites use it.
98
+
99
+ ## [1.11.0] - 2026-07-22
100
+
101
+ ### Added
102
+
103
+ - **`okf dirs <dir|@slug> [--json]`** — the bundle's directories (its clusters)
104
+ with the number of concepts living **directly** in each, root first and the
105
+ total last. Every dir the tree has, including the empty intermediates that
106
+ exist only to connect it — a dir holding nothing but sub-directories reads
107
+ `0`, not a hidden rollup, so the column sums to the bundle's concept count.
108
+ JSON: `{ bundle, total, count, dirs: [{ dir, count, subdirs }] }`.
109
+ - **`--depth N` on `index` and `dirs`** — how many directory levels below the
110
+ starting point to keep, where the starting point is the `--dir` when one is
111
+ given and the bundle root otherwise. Relative rather than absolute, so
112
+ `--dir a/b --depth 1` reads "a/b and one level under it" without first working
113
+ out how deep `a/b` is, and the two flags walk a tree a level at a time.
114
+ `--depth 0` is the starting point alone; anything but a whole number is a
115
+ usage error (exit 2). This is what makes `index` usable at scale — every
116
+ directory in it is a section, so a few hundred concepts is a map nobody reads
117
+ whole. On one 414-concept bundle `index --no-body` went 12.5 KB → 1.3 KB at
118
+ `--depth 1`, and `index --json` 313 KB → 2.8 KB with `--depth 1 --except
119
+ body,listing`.
120
+ - **`--dir` on `index` and `dirs` brings the chain up to the root with it**, so a
121
+ branch is never shown adrift of the authored context that says what it is —
122
+ the root `index.md`'s prose first among it. Those rows print with a leading
123
+ `↑`, carry `ancestor: true`, and stay out of `total`; `--no-ancestors` drops
124
+ them. Ascent and descent are separate axes, so `--depth` never bounds the
125
+ chain: `--dir X --depth 0` is X alone, plus how you get to X. A `--dir` that
126
+ names nothing gains no chain, since a lone root row would read as a partial
127
+ answer to a query that in fact matched nothing.
128
+ - **`dirs` gains `--dir` (repeatable) and a `subtree` count** per row: the
129
+ concepts at or below that directory, defined as exactly what `--dir` on the
130
+ row returns, so the number and the flag can never disagree. Without it a
131
+ truncated listing is all zeroes at the top of a deep tree — which is where
132
+ "where is the mass?" is actually asked. The human table shows the column only
133
+ where some directory nests; `--json` always carries it.
134
+ - **`--dir PATH`** joins the shared filter set on `search`, `catalog`, `files`,
135
+ `types` and `tags`, and `index` gains it as a repeatable selector. One rule:
136
+ a concept matches when its dir *is* the path or sits below it — so `--dir
137
+ platform` reaches `platform/services/api`, `--dir platform/services` narrows,
138
+ and `--dir .` means the root alone with no special case. `root` is the
139
+ unquoted spelling of `.`; matching folds case.
140
+ - **`tags --by dir`** cuts the tag index by the whole directory path, where
141
+ `--by area` only ever saw the first segment.
142
+ - **`stats` gains `dirs` and `by_dir`** (the full-path cut, direct counts), and
143
+ its human breakdown now reads **By dir**. Both are read off the same map `okf
144
+ dirs` lists and `--dir` is answered against, so the two verbs cannot report a
145
+ different number of directories — a directory holding nothing directly appears
146
+ at `0` rather than being dropped, since it is still one `--dir` addresses.
147
+ - **Search rows carry `dir`** — the full path, `.` at the root — beside the
148
+ first-segment `area` they already had.
149
+ - **`dirs` takes `--fields`/`--except`** like the other list views, over the row
150
+ shape it already declared.
151
+ - **A single-bundle `okf server` answers `GET /search?q=`.** The route was the
152
+ hub's alone — conceived as the *cross-bundle* one — which left `okf server
153
+ ./docs` with a ⌘K palette that could find nothing, though one bundle is a legal
154
+ one-element set. `App` owns the payload and the hub calls it, so the shape is
155
+ defined once; a row from a single-bundle server carries no `slug`, which is how
156
+ it avoids answering as if it were a set. A static `okf render` still advertises
157
+ no endpoint: there is no server behind it to ask.
158
+ - **The route always answers; advertising it is the caller's call.** The page
159
+ resolves the endpoint *relative to the URL the reader is on*, so only whoever
160
+ mounted the app knows what to call it: `okf server` mounts at the root and
161
+ passes `search`, while `App.new(folder)` on its own advertises nothing, since
162
+ a default would point an app mounted at `/knowledge` back at its host's root.
163
+ - **The search index is built once and held.** Every request used to rebuild the
164
+ whole corpus — measured 1.45 s per search on a 414-concept bundle, flat across
165
+ repeats, with the build ~95% of it. `Search.prepare` holds a corpus (documents,
166
+ the key→concept map, the built index) and `Search.with` queries it: **0.016 –
167
+ 0.052 s** per search, with the 1.39 s build moved into boot, where `okf server`
168
+ warms it deliberately. An engine opts in by exposing `prepare`; the scan
169
+ declares none and is handed none, so no engine or addon had to change. The
170
+ trade is staleness — a corpus is a snapshot, like the graph — and the hub drops
171
+ its corpus on any registry write, since a held index outliving the set it was
172
+ built from is a wrong answer rather than a slow one.
173
+ - **`⌥ drag` moves a cluster box**, and is listed in the `?` sheet.
174
+
175
+ ### Changed
176
+
177
+ - **A bundle is named by its slug, everywhere it is chosen.** The ⌘K switcher,
178
+ the Bundles panel and the hub's `/b/` page all led with the derived
179
+ `parent/dir` label and left the slug in muted grey beside it — the address in
180
+ the name's place, when a bundle is addressed by `@okf-gem` and `/b/okf-gem/`.
181
+ Rows now carry `@slug` as the name and the folder as the fact under it, shown
182
+ only where it is not the name repeated. `/b/` drops the short label outright:
183
+ it only ever stood in for the full path, which is on the row already.
184
+ - **A `.okf` directory is labelled by the project that holds it.**
185
+ `Bundle::Folder.label` reads `repo/.okf` as `repo`. The `parent/dir` pair
186
+ exists because a bundle directory's own name is rarely unique — except when it
187
+ is `.okf`, the conventional container, and then a registry of eight projects
188
+ was eight rows all saying `.okf`. This also reaches `okf registry list` and the
189
+ default `okf server` title. (A `.okf` with no parent to borrow keeps its own
190
+ name; that case used to compose into `//.okf`.)
191
+ - **A force layout settles, then moves once.** `animate:true` reads to these
192
+ engines as "render every tick of the simulation" — the visible bounce, and
193
+ hundreds of full re-renders for one settle. It is `'end'` now: the same
194
+ simulation run headless, the nodes moved once into the same final positions.
195
+ Past 250 nodes even that transition is dropped, because at that size the move
196
+ itself is the jank.
197
+ - **A cluster box is scenery, not a handle.** Its empty interior is the largest
198
+ drag target on the canvas, so dragging to look around dragged the *directory*
199
+ instead of the view — worse the bigger the cluster. It takes `grabbable:false`
200
+ **and** `pannable:true`: ungrabbable alone stops the box moving, but the node
201
+ still swallows the drag, and it is `pannable` that hands the gesture to the
202
+ canvas the way empty background does. `Alt`+drag gives the handle back, since
203
+ moving a box is a real gesture, just not the constant one — `Alt` rather than
204
+ `Ctrl`, which on macOS is the system secondary click. A tap still opens the
205
+ directory's map.
206
+ - **`f` is no longer a shortcut.** A bare letter bound globally fires on every
207
+ keystroke the page did not route into an input, and fullscreen is not a mode to
208
+ enter by accident. The button stays and is now the only way in; the shortcut
209
+ sheet no longer advertises a key nothing is bound to.
210
+ - **The skill names one first move.** It had prescribed three different ones
211
+ across seven places — SKILL.md, four playbooks and the CLI reference — and that
212
+ disagreement is the deliberation an agent pays for on every retrieval. Every
213
+ site now says `okf dirs` first, then `okf index --dir <branch>` to descend,
214
+ chosen structurally: `dirs` emits one row per *directory* where `index` emits
215
+ one listing row per *concept* even under `--no-body`, so the two scale with
216
+ different things.
217
+
218
+ - **Cluster mode nests.** The graph page grouped concepts into one flat row of
219
+ boxes, one per *first path segment* — the same lossy projection `--area` was.
220
+ A cluster is a directory now, and the boxes nest as the directories do, to a
221
+ depth picked from a select beside the layout one. Depth **1** is the default
222
+ and draws exactly the old view; a flat bundle is offered no control at all.
223
+ At depth N every directory of depth ≤ N gets a box (intermediates that hold no
224
+ concepts of their own included, since they hold sub-boxes), and a concept
225
+ attaches to its own directory's box truncated to N. The root box still holds
226
+ direct-root concepts and never nests another. Box ids carry the directory
227
+ verbatim (`box::platform/services`, `box::.`), so a tap opens that directory's
228
+ map with no label to unmangle.
229
+ - **The page speaks `dir` too**: the filter group is **Dirs**, listing every
230
+ directory (not just first segments) and filtering by the same
231
+ directory-and-below rule `--dir` uses — in the graph, catalog and tags views —
232
+ and the Stats panel's breakdown is **By dir**, keyed by the whole path.
233
+
234
+ ### Fixed
235
+
236
+ - **The rail marks Index while the root map is open.** Index is a shortcut into
237
+ Files, so the two share one `data-view` — and the rail read only that, lighting
238
+ **Files** on the one screen a reader reached by clicking **Index**. The open
239
+ file is what distinguishes them, so it is what the rail reads; a *nested*
240
+ `index.md` is still Files.
241
+ - **`--dir` accepts the label the views print.** `fold_dir` never stripped a
242
+ trailing slash, while `okf index` labels a row `tables/` — so pasting a printed
243
+ row back into the flag matched nothing and exited 0, an empty result under a
244
+ count that agreed with it.
245
+ - **The `--dir` chain keeps its case.** It was walked over case-folded paths and
246
+ then matched against the map with `include?`, which does not fold, so every
247
+ ancestor of a directory spelled with a capital vanished from the chain that
248
+ exists to place the branch.
249
+ - **`--area` with `--depth` or `--dir` is refused (exit 2)** instead of unioning
250
+ the area with what the other flag selects. The deprecated flag is exact: with
251
+ `--depth` it names no starting point to be relative to, and with `--dir` one
252
+ side is exact where the other is a prefix, so the map came back with the area
253
+ *and* the subtree — an answer to neither question. A deprecated flag that
254
+ quietly widens is worse than one that is merely old.
255
+ - **A cleared filter no longer leaves a cluster unlaid.** The tiling runs over
256
+ the visible elements only (fcose throws on a node whose label went
257
+ `display:none` mid-run), but nothing re-tiled when a filter was later loosened —
258
+ so concepts hidden when clustering began came back at their pre-cluster
259
+ coordinates and stretched their box across the canvas. Worst case the filter
260
+ matched nothing, the layout returned early, and clearing it showed a view
261
+ nothing had laid out at all.
262
+ - **A palette hit in a single-bundle server no longer 404s or reloads the page.**
263
+ A row with no `slug` was read as naming a *foreign* bundle, so the href became
264
+ `../undefined/`, the row rendered an "undefined" chip, and the click took the
265
+ page-load branch — reloading the whole index to reach a node already on screen.
266
+ Three sites, one absent field.
267
+ - **A focused form field no longer zooms the page on iOS.** Safari zooms whenever
268
+ a focused control is under 16px and never zooms back out, so on a phone every
269
+ `/` left the reader pinching to recover. Keyed on `(max-width:768px)` *or*
270
+ `(pointer:coarse)`, because neither covers the other — a phone is narrow, a
271
+ tablet in landscape is not and zooms just the same.
272
+ - **A nested cluster no longer throws when a filter empties it mid-layout.**
273
+ fcose measures every node it is handed, so hiding nodes while its tiling
274
+ animation ran threw on a label it could no longer measure. The layout is
275
+ handed the visible elements only — which is also the right answer, since a
276
+ hidden concept has no business influencing where the visible ones land.
277
+ - **An intermediate directory box no longer takes its branch off the canvas.**
278
+ The empty-box rule read a compound's direct children, and a box holding only
279
+ sub-boxes has none, so it always counted as empty. It reads leaf descendants
280
+ now.
281
+
282
+ ### Removed
283
+
284
+ - **`OKF::Server::Hub::SEARCH_LIMIT` and `Hub::SEARCH_ENGINE`.** Both moved to
285
+ `OKF::Server::App`, which now defines the `/search` payload both hosts answer
286
+ with (`App.search_payload`). The hub's copies were left behind unreferenced —
287
+ two constants for one cap is two places to raise it and one of them silently
288
+ losing. Use `OKF::Server::App::SEARCH_LIMIT` / `App::SEARCH_ENGINE`.
289
+
290
+ ### Deprecated
291
+
292
+ - **`--area`, and `tags --by area`.** OKF's own vocabulary for grouping is
293
+ *directories* (`grep -ci area SPEC.md` → 0); "area" was this gem's invention,
294
+ and defining it as a concept id's first path segment threw away every level
295
+ below it. `dir` is now the only machine word — full path, `.` at the root,
296
+ rendered `(root)` for humans — and "cluster" stays prose for what a dir
297
+ groups. Both deprecated spellings keep their **old behavior exactly** and warn
298
+ once per run on stderr (`--json` on stdout is unaffected); they go in a later
299
+ release.
300
+
8
301
  ## [1.10.0] - 2026-07-21
9
302
 
10
303
  ### Added
@@ -930,6 +1223,7 @@ Initial release.
930
1223
 
931
1224
  - Runs on Ruby >= 2.4 with two runtime dependencies: rack and webrick.
932
1225
 
1226
+ [1.11.0]: https://github.com/serradura/okf-gem/compare/v1.10.0...v1.11.0
933
1227
  [1.10.0]: https://github.com/serradura/okf-gem/compare/v1.9.0...v1.10.0
934
1228
  [1.9.0]: https://github.com/serradura/okf-gem/compare/v1.8.0...v1.9.0
935
1229
  [1.8.0]: https://github.com/serradura/okf-gem/compare/v1.7.0...v1.8.0