okf 1.11.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.
@@ -250,8 +250,8 @@ consuming agent is the fuzzy layer — when terms miss, learn the bundle's
250
250
  vocabulary from `tags`/`types` and re-ask in its own words, rather than
251
251
  hammering synonyms or reaching for `--fuzzy` before you have looked. Advisory read: **exit 0 even with zero matches**.
252
252
  JSON, plain-dir mode: `{ bundle, query, count, matches: [{ id, title, type,
253
- area, tags, matched, score, snippet }] }`. Registry mode — any leading @ref,
254
- `@all` among them — swaps the envelope: `{ bundles: [{ slug, dir }, …],
253
+ dir, top_dir, tags, matched, score, snippet }] }`. Registry mode — any leading @ref,
254
+ `@all` or a `@group` among them (a group fans out to its member bundles) — swaps the envelope: `{ bundles: [{ slug, dir }, …],
255
255
  query, count, matches: [{ slug, id, … }] }`; a parser must branch on which form
256
256
  it called. The head maps each slug to its dir once, so a row resolves to
257
257
  `<dir>/<id>.md` without a second lookup and without repeating a path per row.
@@ -357,10 +357,10 @@ All are advisory reads (exit 0) sharing one data source (per-concept metadata pl
357
357
  in/out link degree). Add `--json` to any for a machine substrate.
358
358
 
359
359
  - **`catalog`** — every concept with its metadata (type, status, tags, timestamp,
360
- in/out link degree, description), grouped by top-level area (`dir` on every row
361
- carries the full path). The "what's here, in
360
+ in/out link degree, description), grouped by top-level dir (`dir` on every row
361
+ carries the full path, `top_dir` the first segment). The "what's here, in
362
362
  detail" view. JSON: `{ bundle, count, concepts: [{ id, title, type, description,
363
- tags, timestamp, status, backlog_ref, dir, area, links_out, links_in }] }`.
363
+ tags, timestamp, status, backlog_ref, dir, top_dir, links_out, links_in }] }`.
364
364
  - **`files`** — the folder tree: each concept's filename + title, grouped by
365
365
  directory. The "how it's organised" view. JSON: `{ bundle, count, files: [{ path,
366
366
  id, dir, type, title, description }] }`.
@@ -380,9 +380,9 @@ in/out link degree). Add `--json` to any for a machine substrate.
380
380
  [{ type, count, concepts: [id, …] }] }`.
381
381
  - **`stats`** — bundle rollups: concept / dir / type / cross-link / distinct-tag
382
382
  totals plus per-type and per-dir breakdowns. The "shape at a glance" view. JSON:
383
- `{ bundle, concepts, dirs, areas, concept_types, cross_links, distinct_tags,
384
- by_type, by_dir, by_area }` (`areas`/`by_area` are the deprecated first-segment
385
- cut, kept for one release). `dirs`/`by_dir` cover every directory `okf dirs`
383
+ `{ bundle, concepts, dirs, top_dirs, concept_types, cross_links, distinct_tags,
384
+ by_type, by_dir, by_top_dir }` (`top_dirs`/`by_top_dir` are the first-segment
385
+ rollup). `dirs`/`by_dir` cover every directory `okf dirs`
386
386
  lists — counts are direct, so a directory holding nothing itself is present at
387
387
  `0` rather than missing, and `by_dir.keys` is a complete list of what `--dir`
388
388
  can address.
@@ -442,20 +442,38 @@ page listing the hosted bundles, so a stale bookmark after a rename gets a way
442
442
  home. With **no** dir it serves the *persistent registry*, a plain JSON file
443
443
  under `$OKF_HOME` (default `~/.okf`), managed by the
444
444
  `okf registry` umbrella — like git's `remote` family, and split by what each
445
- verb keys on. **Entry verbs** take a path: `okf registry set <dir>` adds it
445
+ verb keys on.
446
+ **`okf registry init`** creates a *project-local* registry instead: a
447
+ `.okf-registry.json` in the current directory, which okf discovers by walking up
448
+ from the working directory and uses in place of the global one while you are
449
+ inside its tree (the nearest wins, so nested registries resolve nearest-first).
450
+ Every registry op — and every `@slug` — then resolves through it, so a bare
451
+ `okf server` inside a repo serves that repo's bundles with no `$OKF_HOME` setup;
452
+ `okf registry list` names the local file it found. `OKF_NO_DISCOVERY=1` forces
453
+ the global registry — the escape hatch for a fixed-cwd caller (CI, a tool). A
454
+ local registry stores **portable** paths: a bundle inside its tree is written
455
+ relative to the `.okf-registry.json`, so committing the file lets it travel with
456
+ the repo (a checkout elsewhere, a container mounting it) and resolve unchanged;
457
+ a bundle outside the tree stays absolute, since it cannot travel. Paths still read
458
+ back absolute wherever the CLI reports them.
459
+ **Entry verbs** take a path: `okf registry set <dir>` adds it
446
460
  (slug from the basename, or `--as`, which errors on a collision; `--default`
447
461
  puts it first), and because the entry is keyed by path, `set` on an
448
462
  already-registered dir updates it in place — refreshing its title, and renaming
449
- it when `--as` is given. `okf registry del <dir|@slug>` removes one — by name, so an entry whose
450
- directory is already gone still deletes. Slug *or* dir, never both readings at
463
+ it when `--as` is given. `okf registry del <dir|@slug>` removes a bundle *or* a group — by name, so an
464
+ entry whose directory is already gone still deletes, and removing a bundle
465
+ **cascade-drops** it from every group that named it (a group emptied that way is
466
+ deleted). Slug *or* dir, never both readings at
451
467
  once: an argument with a `/` in it names a location and only a location, so
452
468
  `del ./notes` refuses when no entry points there rather than stripping to the
453
469
  slug `notes` and deleting a bundle somewhere else entirely.
454
470
  <!-- rule:okf-registry-del-path-or-slug -->
455
471
  **Slug verbs** take the name — bare, or as an `@slug`: `okf registry default <@slug>`
456
- chooses which bundle `/` opens **by moving that entry to the front**, and
457
- `okf registry rename <@slug> <new>` renames a slug (mount path and switcher
458
- name) — `<new>` is a name being minted, so it is never a ref. The registry is ordered and **the first entry still on disk is the
472
+ chooses which bundle `/` opens **by moving that entry to the front** (a group is
473
+ refused — the default is one bundle), and
474
+ `okf registry rename <@slug> <new>` renames a bundle *or* group slug (mount path
475
+ and switcher name), **cascading** the new name into every group's member list —
476
+ `<new>` is a name being minted, so it is never a ref. The registry is ordered and **the first entry still on disk is the
459
477
  default** — that is the whole rule, so the first bundle you register is the
460
478
  default until you move another one, a rename keeps its position, and a `del`
461
479
  promotes whatever is next. A vanished directory is stepped over (the server
@@ -464,11 +482,25 @@ cannot open one, so starring it would name a bundle `/` never serves), and
464
482
  gives a directory that is not there. The file is hand-editable and reorders
465
483
  visibly, which is the point: there is no stored slug that can dangle.
466
484
  <!-- rule:okf-registry-default-position -->
485
+ **Group verbs** name a *set* of bundles under one slug — a durable subset for the
486
+ two verbs that take several bundles. `okf registry group <slug> <@member…>`
487
+ creates a group, or adds members to one (a union); members are bundle *or* group
488
+ slugs, so groups nest. A group shares the slug namespace with bundles (a slug
489
+ names one *or* the other, never both), `all` stays reserved, and a member set that
490
+ would make the group reach itself is refused. `okf registry ungroup <slug>
491
+ <@member…>` removes members; emptying a group deletes it. A group resolves,
492
+ recursively and path-deduped, to its bundle leaves — which **only `okf search`
493
+ and `okf server` consume**: every single-bundle verb (`lint`, `index`, …) refuses
494
+ a `@group` with exit 2, the same rule that refuses a second bundle. `@all` is
495
+ unchanged — it still names every registered *bundle*, groups being named subsets
496
+ of that.
467
497
  `okf registry list` (or a bare
468
498
  `okf registry`) stars the default and flags vanished dirs `(missing)` — the
469
- server skips those with a note; `--json` answers
499
+ server skips those with a note — and lists any groups with their members and
500
+ resolved leaf count; `--json` answers
470
501
  `{ registry: <file>, count, bundles: [{ slug, title, dir, mount, default,
471
- missing }] }`, naming the file it read so a `$OKF_HOME` mismatch is visible. The hub roster is a
502
+ missing }], groups: [{ slug, members, resolved }] }`, naming the file it read so a
503
+ `$OKF_HOME` mismatch is visible. The hub roster is a
472
504
  **boot-time snapshot**: restart `okf server` after registry changes. Behind a
473
505
  hub the page gains a **bundle switcher** (⌘/Ctrl-K, or the rail button with its
474
506
  bundle-count badge): the current bundle is pinned, the default chipped; ⏎
@@ -481,7 +513,10 @@ them all with the registry's first entry still on disk at `/` — the way to kee
481
513
  several bundles a keystroke apart without re-passing paths.
482
514
  `okf server @a @b` serves a registry subset, each mounted under its registered
483
515
  slug — but as with any dirs-given run, the *first argument* lands at `/`; the
484
- registry's own order applies only to the bundle-less run.
516
+ registry's own order applies only to the bundle-less run. A `@group` argument
517
+ fans out to its member bundles in the same way (`okf server @backend`), its first
518
+ member landing at `/`; `okf search @group <term…>` merges the group's members
519
+ into one ranking, exactly as naming them individually would.
485
520
 
486
521
  **Trust boundary:** the page renders each fetched markdown body through
487
522
  DOMPurify and escapes everything it inlines (every `<` in the graph data is
@@ -522,8 +557,30 @@ only when the task truly consumes every body; for one question, the
522
557
 
523
558
  `--hubs` swaps the dump for the **inbound ranking**: every concept with at
524
559
  least one inbound link, ranked by inbound degree, each with its links grouped
525
- by *source area* (`core/status ×3 flows 2, billing 1`) — the evidence for
560
+ by *source top-level dir* (`core/status ×3 flows 2, billing 1`) — the evidence for
526
561
  [refine](../playbooks/refine.md)'s hub origin test ("is this hub well-homed?").
527
562
  A source at the bundle root counts under `(root)`. JSON: `{ bundle, count,
528
- hubs: [{ id, area, inbound, by_area: { <area>: n } }] }`. Advisory read, exit 0;
563
+ hubs: [{ id, top_dir, inbound, by_top_dir: { <top_dir>: n } }] }`. Advisory read, exit 0;
529
564
  `--minimal`/`--no-body` shape node payloads and change nothing here.
565
+
566
+ `--traffic` asks the same question one grain coarser: **directories**, not
567
+ concepts. Every concept collapses into the directory it lives in and every link
568
+ between two directories collapses into one weighted arc, so a bundle's wiring
569
+ becomes a table you can read — measured on one 47-concept bundle, 227 links
570
+ collapsed into 50 arcs, of which the fitted cut draws 22. Each row carries the
571
+ directory's traffic split three ways
572
+ (`internal` / `out` / `in`) plus **cohesion**, its internal share of the total:
573
+ the evidence for [refine](../playbooks/refine.md)'s container test, where
574
+ `--hubs` only ever answered about concepts. Rows lead with the lowest cohesion,
575
+ so the directories with a case to answer come first, and a directory with no
576
+ traffic at all prints `—` rather than a `0%` it did not earn.
577
+
578
+ `--cut N` is the least arc weight drawn. It defaults to a value **fitted to the
579
+ bundle** — enough arcs for roughly 1.5 per directory, floored at 8 — because a
580
+ fixed weight cannot serve both ends: measured at weight 3 across ten bundles it
581
+ left 2 arcs on one and 136 on another. The JSON says which you got. Cohesion is
582
+ computed over *every* arc and never the drawn ones, so tightening the cut
583
+ changes the picture and never the evidence. JSON: `{ bundle, cut, fitted, dirs:
584
+ [{ dir, parent, count, subtree, internal, out, in, cohesion }], arcs: [{ source,
585
+ target, weight }], total_arcs }` — a fraction of the full dump (2.6 KB against
586
+ 27 KB on that bundle), and the shape rather than the contents. Advisory, exit 0.
data/lib/okf/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OKF
4
- VERSION = "1.11.0"
4
+ VERSION = "1.12.0"
5
5
  end
data/lib/okf.rb CHANGED
@@ -48,6 +48,7 @@ module OKF
48
48
  require "okf/concept"
49
49
  require "okf/bundle"
50
50
  require "okf/bundle/graph"
51
+ require "okf/bundle/skeleton"
51
52
  require "okf/bundle/search"
52
53
  # These two lines ARE the engine preference order. Each engine registers itself
53
54
  # at load, `Search.engines` is registration order, and the router walks it after
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.11.0
4
+ version: 1.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
@@ -93,6 +93,7 @@ files:
93
93
  - lib/okf/bundle/search.rb
94
94
  - lib/okf/bundle/search/index.rb
95
95
  - lib/okf/bundle/search/scan.rb
96
+ - lib/okf/bundle/skeleton.rb
96
97
  - lib/okf/bundle/validator.rb
97
98
  - lib/okf/bundle/validator/result.rb
98
99
  - lib/okf/bundle/writer.rb