okf 1.11.0 → 1.13.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: 7fae00b0ef2e7029cf0869ec73339321f6a54d58bd566aa6139cf6d5143999b9
4
- data.tar.gz: e337a96fadc268338e4f23984bb85b7b6ad54cee9b13f79a8301ae0179d4eec0
3
+ metadata.gz: 84ee2cefa8e04f2358c9125ca00ccdf9de8a12ab0a78a96315ec846bab8d3e87
4
+ data.tar.gz: 7a3186c5aa2924b4425a0c0a86c9f0812948427348d4fd3d0cd46852bd6dc575
5
5
  SHA512:
6
- metadata.gz: ec66b51cd4a4cba56998248a85bf0470ad62db32487f7c5d0c66780057bcdb839b0f6b521fe6649fb438a0010da2c9d3a82b2781fbeb59c90de5d96971b5ab39
7
- data.tar.gz: 181388d7be611d1af0fb811a50a326f7d72b159d4ea09b632007d5a355042faf6bca7c27f6d5d67d396fad03f868450ec2ed0234655b3a7861074b43b3356135
6
+ metadata.gz: b0c9f1002b408702c74f73be001eab10b6691419700d33b41e81bd001d21858f1d79289696edbb2ad6e7c6f4a76ccd4c7458cf211e82fded5224f5fcfa6328b4
7
+ data.tar.gz: 0ed68e0095ab1aa912c5df3b6fd7b9f82e0e07f2cad0c182437cb70021beed8d5ecb054106185204b18d0f138303372bc62202c286aec75ce6c4a43434f2e546
data/CHANGELOG.md CHANGED
@@ -5,6 +5,196 @@ 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.13.0] - 2026-08-07
9
+
10
+ ### Added
11
+
12
+ - **`Bundle#directories`** — the bundle's own answer to "does this bundle have
13
+ directory X?", public because every surface now asks it (the CLI's concept
14
+ views, `okf-mcp`'s `dir` refusal) and the API this version's minor bump
15
+ exists for. Introduced by, and argued in, the `root` fix under Fixed below.
16
+
17
+ ### Changed
18
+
19
+ - **The repository became a monorepo, and the gem moved into `okf/`.** Nothing
20
+ about the published gem changes in kind — same name, same library, same `v*` tag
21
+ series, same image, byte-identical in size to a pre-move build — but a checkout is laid out differently: one
22
+ directory per gem, named for the gem it ships, so `okf-mcp/` and the rest can
23
+ land beside the baseline without reshaping anything. `plugin/`,
24
+ `.claude-plugin/`, `.okf/` and the `Dockerfile` stay at the root; the last of
25
+ those because its build context must be the repo root, where the `.git` the
26
+ gemspec's `git ls-files` needs lives. From a checkout it is `cd okf` for
27
+ everything about the gem, and plain `rake` at the root to run every gem's suite
28
+ or to validate this repo's own bundle.
29
+ - **`CODE_OF_CONDUCT.md` no longer ships in the gem** (70 files, from 71). It is
30
+ a repository document and was packaged only because nothing rejected it. The
31
+ gem now carries **its own README** instead of the project's — which also fixes
32
+ a long-standing wart, since the project README opens with images under
33
+ `.github/` that the gemspec has never packaged, so the page on rubygems.org has
34
+ been rendering a broken hero.
35
+ - `changelog_uri` points at `okf/CHANGELOG.md`, where this file now lives.
36
+
37
+ ### Fixed
38
+
39
+ - **A directory really named `root` is nameable again.** `--dir` and `--area`
40
+ accept `root` as a spelling of the bundle root, which is worth a shell quote
41
+ right up until a bundle actually has a `root/` directory — and then the alias
42
+ made it unaddressable, answering for the bundle root instead: the wrong
43
+ concepts, exit 0, nothing said. The alias now yields to a real directory,
44
+ since a convenience is worth less than a directory being reachable at all;
45
+ where no such directory exists nothing changes, which is every bundle that
46
+ has ever relied on the spelling. `okf dirs` carried the same fold into its
47
+ `subtree` column, where it computed the `root` row's subtree against the
48
+ bundle root — so the number on the row disagreed with what `--dir` on that
49
+ row returns, the one thing it promises never to do.
50
+
51
+ "Does this bundle have a `root` directory?" is now asked of the bundle
52
+ (`Bundle#directories`, under Added above) rather than of whichever list a view
53
+ had to hand. The concept views were reading it off the *catalog*, which knows
54
+ only directories holding concepts, so a `root/` carrying nothing but an
55
+ `index.md` stayed folded in `catalog`/`files`/`tags`/`types`/`search` while
56
+ `dirs` and `index` named it correctly — two answers to one question about one
57
+ bundle. And a multi-bundle `okf search @a @b --dir root` resolved per bundle
58
+ inside the loop, so one flag meant the `root/` subtree in the bundle that has
59
+ one and the bundle root in the bundle that does not, merged into a single
60
+ ranking with nothing in the output saying so; the served set resolves it once.
61
+
62
+ `Bundle#directories` counts every file kind that makes a directory real —
63
+ concepts, an `index.md`, and now a scoped `log.md`, which the first cut left
64
+ out: a `root/` holding only its history was invisible to the set, so the
65
+ alias won again for exactly the shape the fix was named after, one file kind
66
+ over. The same seed feeds `directory_index`, so `dirs`, `index` and `stats`
67
+ count a log-only directory too, and every surface answers "does this bundle
68
+ have directory X?" from one list.
69
+ - **`filter_entries` regained its 1.12.0 arity.** The dirs-source fix above
70
+ added a required third parameter to a helper on `CLI::Command` — the base
71
+ every verb inherits, plugin gems' verbs included — so an out-of-tree verb
72
+ calling the released two-argument shape raised `ArgumentError` at runtime.
73
+ The parameter defaults now, and the default is 1.12.0's resolution — the
74
+ helper is handed rows, not the folder, so it has nothing to resolve a real
75
+ `root/` against, and the `root` alias folds exactly as it did; the plugin
76
+ suite drives a 1.12.0-era verb through the two-argument shape to pin it.
77
+ Its sibling `filter_ids` makes the opposite choice on purpose: it *is*
78
+ handed the folder, and its two-argument shape resolves through `dir_scope`,
79
+ because the unconditional fold there was the bug this entry fixes, not a
80
+ contract — an old caller gets the corrected answer rather than the familiar
81
+ wrong one. In-tree callers pass
82
+ the set through `dir_scope`, which also derives it only when a `--dir` or
83
+ `--area` flag will actually consult it — a plain listing no longer pays a
84
+ per-path ancestor walk for a set nothing reads (and `Bundle#directories` is
85
+ memoized, so the verbs that ask twice pay once).
86
+ - **Coverage stopped measuring the plugin's curation hook** for one commit, and
87
+ said so by going *up*: SimpleCov's root defaults to the working directory, so
88
+ moving the gem down a level dropped ~100 tested lines out of the report and
89
+ line coverage read 98.63% against 98.47%. Its root is the repository now, with
90
+ the report still written inside the gem.
91
+
92
+ ### Security
93
+
94
+ - **A symlinked file in a bundle can no longer read past the bundle root.** The
95
+ traversal guard `Path.join_under!` was lexical — it expands the path string,
96
+ and `File.expand_path` does not resolve a symlink — so a link whose name sat
97
+ inside the root but whose target did not passed the check, and the read
98
+ followed it. Every read now realpath-resolves and re-checks against the real
99
+ root: `Path.under?` is the shared pure predicate, `Bundle::Reader` quarantines
100
+ an escaping file into the unparseable bucket (a planted symlink is one bad
101
+ file, not grounds to fail the whole bundle read, which would hand any writer of
102
+ a served directory a denial of service), and `Concept::File` guards its own
103
+ read. A symlink that stays inside the bundle still resolves as before. The gap
104
+ reached every reader; serving a bundle's contents to an agent — as `okf-mcp`
105
+ does — is what surfaced it.
106
+
107
+ ## [1.12.0] - 2026-07-24
108
+
109
+ ### Added
110
+
111
+ - **`okf graph --traffic`** — the link graph read one grain coarser. `--hubs`
112
+ measures concepts, but the refine playbook's directory judgements ("does this
113
+ directory prune? a concern, or a container?") had nothing measured at their
114
+ grain. `--traffic` collapses each concept into its directory and the links
115
+ between two directories into one weighted arc, so every row carries its
116
+ internal / out / in traffic and a **cohesion** — its internal share of that
117
+ total. That is cohesion versus coupling on a knowledge tree: near-zero cohesion
118
+ under heavy inbound is a shared vocabulary doing its job, heavy outbound with
119
+ nothing back is a projection wearing a directory. The rows sort by cohesion
120
+ ascending, so the directories with a case to answer come first, and a directory
121
+ with no traffic at all prints `—` rather than a `0%` it did not earn. The arc
122
+ **cut** is fitted to the bundle, not fixed — at weight 3, ten bundles ranged
123
+ from 2 arcs to 136 — and `--cut N` overrides it; cohesion is computed over
124
+ *every* arc regardless, so narrowing the drawn picture never moves the
125
+ evidence. JSON: `{ bundle, cut, fitted, dirs, arcs, total_arcs }`. The pure
126
+ model underneath is `Bundle::Skeleton` — concepts folded to directories, links
127
+ to weighted arcs, every edge tagged with the cut it survives (a local-degree
128
+ sparsifier, union rule, so no linked concept is ever stranded) — and it does no
129
+ I/O and draws nothing: it names a cut rather than taking one, so both the graph
130
+ page and the CLI narrow the same emission their own way.
131
+ - **The graph page draws links in three amounts, and opens dense on its spine.**
132
+ 227 links over 47 concepts at degree 9.7 is unreadable because of its arrows,
133
+ not its dots — so links become a layer: **every** link, the **spine** (each
134
+ concept's strongest edge — the Skeleton's `keep_at===0` set, chosen so it
135
+ touches every linked concept), or **none**, with a selected concept's own links
136
+ always shown in full. A dense bundle now opens on its spine rather than greeting
137
+ the reader with the thicket; the trigger is undirected degree above a floor set
138
+ between a tree's ~2 and the 9.7 that drove it, and `--map` overrides to none
139
+ with the directories boxed. Above 800 edges the first layout runs on the spine
140
+ alone and the rest arrive a frame later with no re-layout — **6.25 s → 3.29 s**
141
+ on 414 concepts, nothing on screen moving. Proven in both render modes on a
142
+ committed 110-concept / 880-link fixture, the only one over both floors.
143
+ - **Registry groups — a named, recursive set of bundles.** A group is a registry
144
+ slug that names a list of members (bundle *or* group slugs, so they nest) and
145
+ resolves recursively, path-deduped, to its bundle leaves. `okf registry group
146
+ <slug> <@member…>` creates one or adds to it, `ungroup` removes members (and
147
+ emptying a group deletes it), and `del`/`rename` now span a group slug too —
148
+ one name cascades across every member list, one `del` cascade-drops the slug and
149
+ deletes any group it empties. Groups live in their own list in the registry
150
+ JSON, so the first-is-default rule and the `File.directory?` guards never meet a
151
+ pathless entry. `okf search @backend` merges the members into one ranking and
152
+ `okf server @backend` mounts each (the first at `/`), both skipping a vanished
153
+ member with a note, exactly as `@all` does. Every single-bundle verb **refuses**
154
+ a `@group` (exit 2) — the same rule that refuses a second bundle, through the
155
+ same `resolve_registered` seam.
156
+ - **`okf registry init`** — create a project-local `.okf-registry.json` in the
157
+ current directory. Once it exists, okf discovers it by walking up from the
158
+ working directory, and every registry operation — and every `@slug` — resolves
159
+ through it in place of the global `$OKF_HOME` registry, so a bare `okf server`
160
+ inside a repo serves that repo's bundles with no global setup. The nearest one
161
+ on the path wins (nested registries resolve nearest-first), `okf registry list`
162
+ names the local file it found, and `OKF_NO_DISCOVERY=1` forces the global one —
163
+ the escape hatch for a fixed-cwd caller (CI, a tool).
164
+ - **A local registry stores portable, relative paths.** A bundle inside the
165
+ registry's own tree is written relative to the `.okf-registry.json`, so the file
166
+ can be committed and travels with the repo — a checkout on another machine, or a
167
+ container that mounts it, resolves the same bundles unchanged. A bundle outside
168
+ the tree keeps an absolute path (it cannot travel). Paths still read back
169
+ absolute everywhere the CLI reports them; the relative form lives only on disk,
170
+ and an existing absolute local entry migrates to relative on its next write. The
171
+ global `$OKF_HOME` registry is unchanged — it stores absolute paths as before.
172
+
173
+ ### Changed
174
+
175
+ - **The derived `area` field is renamed `top_dir`** — the first-path-segment
176
+ rollup the catalog, search, `stats`, and `graph --hubs` carry. `area` was never
177
+ the OKF spec's word (the spec speaks only of `dir`), so the rollup now names
178
+ itself in the spec's vocabulary: it is the `dir` at the top level. The `--json`
179
+ keys move with it — catalog/search rows carry `top_dir` in place of `area`,
180
+ `stats` emits `top_dirs`/`by_top_dir`, and `graph --hubs` emits
181
+ `top_dir`/`by_top_dir`. The **deprecated `--area`/`--by area` input flags are
182
+ unchanged** — they still warn and map to `--dir`/`--by dir`, and now source the
183
+ renamed field internally. No behavior changes; the well-homed-hub numbers are
184
+ identical.
185
+
186
+ ### Fixed
187
+
188
+ - **A local-registry server preserves its relative-path anchor across a
189
+ re-open.** The hub re-read its boot registry with `OKF::Registry.new(path)`,
190
+ which drops the `relative_base` a discovered `.okf-registry.json` carries — so
191
+ on a project-local server the Bundles panel matched each mounted bundle's
192
+ absolute root against the re-read entry's *relative* path, missed, and drew
193
+ every in-tree bundle as "folder is gone", while a browser add flattened the new
194
+ bundle to an absolute path, undoing the portability relative storage exists for.
195
+ `Registry#reopen` re-reads the same file anchored the same way, and both hub
196
+ re-open sites use it.
197
+
8
198
  ## [1.11.0] - 2026-07-22
9
199
 
10
200
  ### Added
@@ -205,7 +395,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
205
395
  rendered `(root)` for humans — and "cluster" stays prose for what a dir
206
396
  groups. Both deprecated spellings keep their **old behavior exactly** and warn
207
397
  once per run on stderr (`--json` on stdout is unaffected); they go in a later
208
- release, along with `by_area` and the `area` row field.
398
+ release.
209
399
 
210
400
  ## [1.10.0] - 2026-07-21
211
401
 
@@ -1132,6 +1322,8 @@ Initial release.
1132
1322
 
1133
1323
  - Runs on Ruby >= 2.4 with two runtime dependencies: rack and webrick.
1134
1324
 
1325
+ [1.13.0]: https://github.com/serradura/okf-gem/compare/v1.12.0...v1.13.0
1326
+ [1.12.0]: https://github.com/serradura/okf-gem/compare/v1.11.0...v1.12.0
1135
1327
  [1.11.0]: https://github.com/serradura/okf-gem/compare/v1.10.0...v1.11.0
1136
1328
  [1.10.0]: https://github.com/serradura/okf-gem/compare/v1.9.0...v1.10.0
1137
1329
  [1.9.0]: https://github.com/serradura/okf-gem/compare/v1.8.0...v1.9.0