okf 2.0.0 → 2.1.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 +27 -0
- data/README.md +9 -5
- data/lib/okf/cli.rb +20 -2
- data/lib/okf/skill/SKILL.md +15 -7
- data/lib/okf/skill/playbooks/maintain.md +3 -1
- data/lib/okf/skill/playbooks/produce.md +4 -2
- data/lib/okf/skill/reference/authoring.md +23 -26
- data/lib/okf/skill/reference/cli/checks.md +171 -0
- data/lib/okf/skill/reference/cli/graph.md +49 -0
- data/lib/okf/skill/reference/cli/map.md +98 -0
- data/lib/okf/skill/reference/cli/registry.md +70 -0
- data/lib/okf/skill/reference/cli/search.md +130 -0
- data/lib/okf/skill/reference/cli/serve.md +83 -0
- data/lib/okf/skill/reference/cli/views.md +59 -0
- data/lib/okf/skill/reference/cli.md +33 -603
- data/lib/okf/skill/reference/spec-map.md +32 -0
- data/lib/okf/version.rb +1 -1
- metadata +9 -1
|
@@ -5,6 +5,32 @@
|
|
|
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.
|
|
8
|
+
## Which file answers what
|
|
9
|
+
|
|
10
|
+
This file is the **index and the shared contract**: what every verb has in
|
|
11
|
+
common — refs, exit codes, `--json`, the filters — lives here, and each verb's
|
|
12
|
+
own semantics, traps, and JSON shape live in one file below. Read this one, then
|
|
13
|
+
the single row the question calls for, and stop; a leaf names its prerequisite
|
|
14
|
+
when it has one.
|
|
15
|
+
|
|
16
|
+
| File | Verbs | What it answers |
|
|
17
|
+
|------|-------|-----------------|
|
|
18
|
+
| [cli/checks.md](cli/checks.md) | `validate` `lint` `loose` `references` | what makes a bundle non-conformant, every lint check and the severity it is pinned at, the two clocks that share the `stale_after` spelling, why loose ≠ orphan, which dangling pointers each surface can see |
|
|
19
|
+
| [cli/search.md](cli/search.md) | `search` | ranked retrieval over metadata and bodies — exact by default, the two engines and what the index silently loses, `@all` across bundles, the two JSON envelopes |
|
|
20
|
+
| [cli/map.md](cli/map.md) | `index` `dirs` | orientation: the §8 directory map and the cluster sizes, how `--dir`/`--depth`/ancestors compose, what a synthesized listing means, how not to page the bundle |
|
|
21
|
+
| [cli/views.md](cli/views.md) | `catalog` `files` `tags` `types` `stats` | the browser panels as text — per-concept metadata, the folder tree, tag and type rollups, bundle totals, and the JSON each emits |
|
|
22
|
+
| [cli/serve.md](cli/serve.md) | `server` `render` | the interactive page and its static twin — what renders, what is fetched live, many bundles behind one hub, the trust boundary both share |
|
|
23
|
+
| [cli/registry.md](cli/registry.md) | `registry` (`init` `set` `del` `default` `rename` `group` `ungroup` `list`) | naming bundles once: which file is written and how it is found, path-keyed vs slug-keyed verbs, groups, why the default is a position |
|
|
24
|
+
| [cli/graph.md](cli/graph.md) | `graph` | the raw node/edge dump and what it costs, plus the two rankings [refine](../playbooks/refine.md) reads — `--hubs` by concept, `--traffic` by directory |
|
|
25
|
+
|
|
26
|
+
A question that ends in *what does the spec actually say* leaves the CLI
|
|
27
|
+
entirely: [spec-map.md](spec-map.md) names the § and
|
|
28
|
+
[SPEC.md](SPEC.md) carries its words.
|
|
29
|
+
|
|
30
|
+
A verb `okf help` lists and no row here documents is an **extension's**, not a
|
|
31
|
+
gap in the docs — ask `okf <verb> --help`. A row whose file is missing is a note
|
|
32
|
+
to make, not a reason to stop: grep `reference/cli/` for the verb and proceed.
|
|
33
|
+
<!-- rule:okf-cli-index -->
|
|
8
34
|
|
|
9
35
|
## When it isn't installed
|
|
10
36
|
|
|
@@ -24,7 +50,8 @@ cannot: each verb's semantics, its traps, and its JSON shape.
|
|
|
24
50
|
its own, listed under `installed extensions:` in `okf help`. So a verb that
|
|
25
51
|
`--help` shows and this file does not document is **normal, not a documentation
|
|
26
52
|
error** — ask `okf <verb> --help` for it, and expect nothing here about its
|
|
27
|
-
semantics or JSON.
|
|
53
|
+
semantics or JSON. Every file this index routes to documents the built-ins
|
|
54
|
+
only.
|
|
28
55
|
|
|
29
56
|
**`--json` is compact by design.** Every emitting verb prints single-line JSON —
|
|
30
57
|
the token-efficient substrate you consume; `--pretty` (which implies `--json`)
|
|
@@ -41,7 +68,7 @@ for a field you dropped — e.g. `okf index <dir> --except body,listing` is the
|
|
|
41
68
|
directory *skeleton* (structure + rollups), and on a large bundle that is the
|
|
42
69
|
difference between a few hundred bytes and hundreds of KB, since the per-item rows
|
|
43
70
|
(`listing`) dominate at scale. `okf index --no-body` is shorthand for dropping just
|
|
44
|
-
`body`.
|
|
71
|
+
`body`. <!-- rule:okf-project-json -->
|
|
45
72
|
|
|
46
73
|
**Every output names its bundle.** Two keys, one meaning each: `bundle` is
|
|
47
74
|
always a directory, `slug` always a registry slug. Name a bundle by `@slug` and
|
|
@@ -73,407 +100,12 @@ cannot open at all — is skipped and noted on stderr, never fatal. The note cou
|
|
|
73
100
|
`server` mounts them; hand a second bundle to any other verb — two dirs, two refs,
|
|
74
101
|
or a mix — and it is a usage error (exit 2), never a silent answer about the first.
|
|
75
102
|
To ask the same question of several bundles, ask `search`, or ask each in turn.
|
|
103
|
+
<!-- rule:okf-one-bundle-per-verb -->
|
|
76
104
|
|
|
77
|
-
##
|
|
105
|
+
## The shared filters — `--type` `--dir` `--tag` `--status` `--trust`
|
|
78
106
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- **§11 cond. 1** every non-reserved file has a parseable YAML frontmatter block;
|
|
82
|
-
- **§11 cond. 2** every such block has a non-empty `type`;
|
|
83
|
-
- **§11 cond. 3** any `index.md`/`log.md` present follows §8/§9 (a nested
|
|
84
|
-
`index.md` has no frontmatter, a root `index.md` carries only `okf_version`,
|
|
85
|
-
`log.md` date headings are ISO `YYYY-MM-DD`).
|
|
86
|
-
|
|
87
|
-
`ERROR`s are the three conditions above; the bundle is non-conformant until every
|
|
88
|
-
one is fixed. `warn`s are soft — missing recommended fields, non-list tags, an
|
|
89
|
-
unparseable timestamp, **broken cross-links, which §6.1 explicitly tolerates**,
|
|
90
|
-
the shape of every §5/§10 family (`generated` not a mapping, a non-integer
|
|
91
|
-
`usage_count`, a `stale_after` that is not `YYYY-MM-DD`, a missing `runtime` on
|
|
92
|
-
an Attested Computation, …), and an `okf_version` the gem does not know (read
|
|
93
|
-
best-effort under §12; an absent one never warns). Absence of an optional family
|
|
94
|
-
is never a fault — a pure v0.1 bundle validates with zero warnings.
|
|
95
|
-
|
|
96
|
-
In `--json`, every warning carries `check` (a stable id) and `source` — `spec`
|
|
97
|
-
when the SPEC's own words state the rule, `convention` for a shape this gem asks
|
|
98
|
-
for beyond them (`verified[].by` presence, integer `usage_count`, a per-entry
|
|
99
|
-
`usage_window` mapping, `parameters[].name`, `executor`/`attester` `resource`).
|
|
100
|
-
Gate on `source` when you want only the spec-normative set; errors keep their
|
|
101
|
-
two-key `{ path, message }` shape. Fix warnings when cheap; never block on them.
|
|
102
|
-
Use `--json` in CI.
|
|
103
|
-
|
|
104
|
-
## lint — curation quality (advisory)
|
|
105
|
-
|
|
106
|
-
Asks the complementary question to `validate`: not "is this legal OKF?" but "is
|
|
107
|
-
this well-curated, navigable, trustworthy?" — precisely over the things §11
|
|
108
|
-
forbids `validate` from rejecting. It has its own report, never emits
|
|
109
|
-
conformance errors, and **exits `0` even with findings** unless you pass
|
|
110
|
-
`--fail-on warn` (exit 1 on any `warn` finding) or `--fail-on info` (exit 1 on
|
|
111
|
-
any finding at all).
|
|
112
|
-
|
|
113
|
-
**Severity is API.** Every check has a pinned level — `warn` or `info` — and
|
|
114
|
-
machine consumers gate on it, so the levels below are stable, not advisory. A
|
|
115
|
-
finding you want to gate on that is `info` gets `--fail-on info` (usually with
|
|
116
|
-
`--only`), never a hope that its severity changes.
|
|
117
|
-
|
|
118
|
-
Eight categories, each backed by individual checks (severity in brackets):
|
|
119
|
-
|
|
120
|
-
- **Reachability** — `orphan` [warn], `not_in_index` [warn],
|
|
121
|
-
`disconnected_component` [info], `unlinked` [info]
|
|
122
|
-
- **Backlog** — `missing_concept` [info], `broken_index_entry` [warn]
|
|
123
|
-
- **Completeness** — `stub` [info], `missing_title` [info],
|
|
124
|
-
`missing_description` [info], `missing_generated` [info] (quiet on either
|
|
125
|
-
spelling — a legacy `timestamp` still counts as a recorded change)
|
|
126
|
-
- **Freshness** — `expired` [info] (§5.5: past the concept's own declared
|
|
127
|
-
`stale_after`, on the day itself), `stale` [warn] (older than the
|
|
128
|
-
reader-supplied `--stale-after` cutoff, keyed on `generated_at`)
|
|
129
|
-
- **Provenance** — `uncited_external` [info] (external body links and no
|
|
130
|
-
sources, in either spelling), `broken_source` [warn] (an in-bundle `.md`
|
|
131
|
-
source target that names no concept; URLs and scope descriptors are out of
|
|
132
|
-
scope, and a non-`.md` asset is out of reach — the reader models concepts,
|
|
133
|
-
so lint never sees the file; `okf references` is the view that checks those
|
|
134
|
-
pointers), `unattributed_claim` [warn] (a footnote
|
|
135
|
-
no `sources[].id` answers — it *misattributes* a claim, which is why it
|
|
136
|
-
outranks its join-twin), `unused_source` [info] (a keyed source no footnote
|
|
137
|
-
cites — slack, not a defect), `unprefixed_actor` [info] (a `verified[].by`
|
|
138
|
-
outside §7's three forms reads as machine-confirmed; a `generated.by`
|
|
139
|
-
outside them feeds no tier but leaves a reader unable to tell a person
|
|
140
|
-
from a process; info so it informs, never blocks). A missing `generated.by` is the *validator's* warning —
|
|
141
|
-
REQUIRED-within is shape, not curation — so lint never double-reports it
|
|
142
|
-
- **Attestation** — `incomplete_computation` [warn] (an Attested Computation
|
|
143
|
-
providing its computation neither way, or both ways — §10.3 says a
|
|
144
|
-
`computation:` path is used *instead of* the body fence),
|
|
145
|
-
`broken_attestation_ref` [warn] (on an `Attested Computation`, a
|
|
146
|
-
`computation`, `executor.resource` or `attester.resource` naming an
|
|
147
|
-
in-bundle `.md` that is not there — a contract no consumer can follow; the
|
|
148
|
-
keys are read only on that type, since §4.1 lets any other concept use them
|
|
149
|
-
for its own purpose). Its reach is exactly the `.md` files: URLs are out
|
|
150
|
-
of scope, and a `.sql` or `.py` target is invisible to *every* check here,
|
|
151
|
-
because the linter reads the concept model and the model carries only
|
|
152
|
-
markdown — `okf references` is the surface that sees those files and reports
|
|
153
|
-
a pointer that misses, whatever the extension. Remember §6.2 reads a bare
|
|
154
|
-
`references/…` as relative to the concept, so from a nested concept it wants
|
|
155
|
-
the leading `/`
|
|
156
|
-
- **Migration** — `legacy_timestamp` [info], `legacy_citations` [info]: one
|
|
157
|
-
finding per bundle naming the files still in a retired v0.1 spelling, with
|
|
158
|
-
the rewrite instructions in the message. Info on purpose — §13 says a v0.1
|
|
159
|
-
bundle is consumable forever, so `--fail-on warn` must not turn red on one.
|
|
160
|
-
A migration campaign gates explicitly:
|
|
161
|
-
`okf lint <dir> --only legacy_timestamp,legacy_citations --fail-on info`,
|
|
162
|
-
exit 1 until clean.
|
|
163
|
-
- **Hygiene** — `duplicate_title` [info], `unused_reference_def` [info],
|
|
164
|
-
`undefined_reference` [warn], `self_link` [info], `log_order` [info] (§9
|
|
165
|
-
reads a log newest-first; disorder is slack, never a §11 error)
|
|
166
|
-
|
|
167
|
-
`--only` / `--except` filter by the **individual check names above**, not the
|
|
168
|
-
category labels — `okf lint <dir> --only orphan,stub` works; `--only reachability`
|
|
169
|
-
is an error. Two knobs tune specific checks: `--min-body N` sets the `stub` body
|
|
170
|
-
threshold in characters (default 50), and `--stale-after DUR` sets the `stale`
|
|
171
|
-
cutoff — a duration like `90d` or `12w`, or an ISO date like `2026-01-01` (a bare
|
|
172
|
-
number is rejected).
|
|
173
|
-
|
|
174
|
-
**Two different clocks, one unlucky name.** The `--stale-after` *flag* and the
|
|
175
|
-
`stale_after:` *frontmatter field* are different mechanisms that happen to share
|
|
176
|
-
a spelling. The flag is the **reader's** age cutoff: "flag anything not touched
|
|
177
|
-
since DUR", keyed on `generated_at`, feeding the `stale` check. The field is the
|
|
178
|
-
**author's** declared expiry: "do not trust this past DATE", feeding the
|
|
179
|
-
`expired` check. Never read one as the other, and never show them adjacent
|
|
180
|
-
without the distinction.
|
|
181
|
-
|
|
182
|
-
**The clock is explicit.** `expired` compares against a day the CLI supplies —
|
|
183
|
-
today by default, or `--today YYYY-MM-DD` for a reproducible report (CI wants
|
|
184
|
-
this). The pure library runs no clock check unless handed `today:`, and every
|
|
185
|
-
clock-gated check that was selected but could not run is *named* in
|
|
186
|
-
`stats.skipped_checks` (the human report prints one `skipped:` line) — a gate
|
|
187
|
-
that is sometimes absent and does not confess converts "unchecked" into
|
|
188
|
-
"checked and fine".
|
|
189
|
-
|
|
190
|
-
The report's stats carry the bundle's posture too: `trust` (the §5.3 tier
|
|
191
|
-
distribution, in the hyphenated wire spelling) and `status` (effective-status
|
|
192
|
-
frequency).
|
|
193
|
-
|
|
194
|
-
`lint --json` is the structured substrate you consume to reason about the two
|
|
195
|
-
things lint deliberately does **not** compute — contradictions and *semantic*
|
|
196
|
-
staleness — which need understanding of meaning.
|
|
197
|
-
|
|
198
|
-
## loose — files with no graph connections (by folder)
|
|
199
|
-
|
|
200
|
-
Lists the **loose** files — concepts with graph **degree 0**: no cross-links in
|
|
201
|
-
*or* out — grouped by folder. It is a focused, folder-organized view over `lint`'s
|
|
202
|
-
`unlinked` check (`okf loose <dir>` ≈ `okf lint <dir> --only unlinked`, regrouped),
|
|
203
|
-
for the "which files float in the graph?" question. Advisory: **exits `0`**; `--json`
|
|
204
|
-
emits `{ bundle, count, loose: [{ id, title, dir }] }`.
|
|
205
|
-
|
|
206
|
-
**Loose ≠ orphan** — the trap. `lint`'s `orphan` is about *reachability*, and an
|
|
207
|
-
`index.md` listing makes a file reachable, so an indexed file is never an orphan.
|
|
208
|
-
But an index listing is **not a graph edge**: a file can be listed in an index yet
|
|
209
|
-
have no cross-links, so it floats in the graph while `lint` reports it as reachable.
|
|
210
|
-
`loose`/`unlinked` catch exactly that gap. A loose file is not automatically a
|
|
211
|
-
defect — a terminal leaf (a backlog item, a spec reference) can be loose by design;
|
|
212
|
-
`loose` surfaces the set so you can judge intent (see the
|
|
213
|
-
[maintain playbook](../playbooks/maintain.md)).
|
|
214
|
-
|
|
215
|
-
## search — ranked text retrieval (metadata + body)
|
|
216
|
-
|
|
217
|
-
The browser page's search brought to the CLI and extended to bodies, so "which
|
|
218
|
-
concept covers X?" costs rows, not body reads. `okf search <dir> <term…>`:
|
|
219
|
-
terms AND together — every term must hit at least one searched field, not
|
|
220
|
-
necessarily the same one — matched **literally against raw text** by default, or
|
|
221
|
-
as Ruby regular expressions with `--regexp`/`-e` (an invalid pattern is a usage
|
|
222
|
-
error, exit 2). `--fuzzy` forgives typos; pairing it with `-e` is a usage error,
|
|
223
|
-
since a pattern is matched literally rather than by edit distance.
|
|
224
|
-
`--in a,b` restricts the searched fields (title, id, tags, type, description,
|
|
225
|
-
sources, body — `sources` is each entry's title and resource joined, so a
|
|
226
|
-
migrated bundle keeps the recall its `# Citations` body text used to give it);
|
|
227
|
-
the shared `--type/--dir/--tag/--status/--trust` filters narrow the candidates
|
|
228
|
-
*first*, so a search scoped by what `index` taught you stays surgical.
|
|
229
|
-
|
|
230
|
-
**The default is exact, so an exact query means what it looks like.** A phrase in
|
|
231
|
-
one argument (`"dedup key"`), a dotted version (`7.2.0`), an underscored
|
|
232
|
-
identifier (`customer_id`), a mid-word fragment (`ustomer`) and a word written in
|
|
233
|
-
`backticks` all match literally. This is what the scan engine buys, and it is the
|
|
234
|
-
default precisely because those queries are the common ones and the alternative
|
|
235
|
-
loses them silently. <!-- rule:okf-search-exact-identifiers -->
|
|
236
|
-
|
|
237
|
-
**`--engine index` is the other engine, and the one to reach for when ranking
|
|
238
|
-
matters more than exactness.** The engine is normally chosen by what the query
|
|
239
|
-
needs — `--fuzzy` routes to the index, anything else stays on the default scan —
|
|
240
|
-
and nothing is printed about the choice. `--engine NAME` overrides that for the
|
|
241
|
-
case the flags cannot express: a matching *model* requires no capability, so no
|
|
242
|
-
flag selects one. Under the index, terms match whole tokens and their prefixes
|
|
243
|
-
(`dedup` finds `deduplication`), rows rank by BM25+, and it is the engine the
|
|
244
|
-
browser page runs — so name it when reconciling a CLI answer with the page. The
|
|
245
|
-
cost is real: its tokenizer splits on punctuation, so identifiers shatter
|
|
246
|
-
(`customer_id` → `customer` + `id`), an infix finds nothing, and a backtick is
|
|
247
|
-
never split off at all, so a word inside a code span is unfindable — a large
|
|
248
|
-
silent loss, since technical prose is full of them. **Do not count on ranking to
|
|
249
|
-
rescue it** — BM25 normalizes by field length, so a short concept dense in `7`,
|
|
250
|
-
`2` and `0` can outrank the one that actually says `7.2.0`. Naming an engine that
|
|
251
|
-
cannot do what you also asked (`--engine index -e`) is a usage error naming one
|
|
252
|
-
that can. <!-- rule:okf-search-engine-choice -->
|
|
253
|
-
|
|
254
|
-
**The capabilities, and which engine has them.** An engine is selected by what
|
|
255
|
-
the query *requires*; only a matching model has to be named, because requiring
|
|
256
|
-
nothing is not something a flag can express:
|
|
257
|
-
|
|
258
|
-
| Flag | Capability | Engine | What it does |
|
|
259
|
-
|---|---|---|---|
|
|
260
|
-
| *(none)* | — | scan | literal substring over raw text; scores by summed field weight |
|
|
261
|
-
| `-e` / `--regexp` | `regexp` | scan | each term is a Ruby regexp, case-insensitive; invalid → exit 2 |
|
|
262
|
-
| `--fuzzy` | `fuzzy` | **index** | edit distance 0.2 × term length — and switches engine |
|
|
263
|
-
| `--engine index` | — | index | whole-token + prefix matching, BM25+ ranking, browser parity |
|
|
264
|
-
| `--engine scan` | — | scan | the default, spelled out |
|
|
265
|
-
|
|
266
|
-
Two consequences worth holding. **`--fuzzy` is an engine switch, not a mode**: it
|
|
267
|
-
carries the whole index with it, so a run that wanted one typo forgiven also gets
|
|
268
|
-
shattered identifiers and unfindable code spans — fix the spelling and stay on
|
|
269
|
-
the default when you can. And **`-e` moves nothing** now, because the default
|
|
270
|
-
engine already offers `regexp`; it changes how a term is *read* (pattern rather
|
|
271
|
-
than literal), not where it is matched. <!-- rule:okf-search-fuzzy-is-a-switch -->
|
|
272
|
-
|
|
273
|
-
`prefix` is a capability the index declares but no flag selects — it is always on
|
|
274
|
-
there. **It is not a reason to reach for the index**: a substring match already
|
|
275
|
-
covers every prefix and then some, so `dedup` finds `deduplication` under both
|
|
276
|
-
engines, while `duplication` and `uplicat` find it under the default only. Prefix
|
|
277
|
-
is what the index needs to catch up to raw text, not a capability it adds on top.
|
|
278
|
-
The index's real advantages over the default are exactly three — relevance
|
|
279
|
-
ranking, typo tolerance, and page parity.
|
|
280
|
-
|
|
281
|
-
**Search spans bundles.** Leading @refs pick several registered bundles
|
|
282
|
-
(`okf search @handbook @notes auth`); **`@all`** is the ref that means every one.
|
|
283
|
-
Rows from different bundles are ranked together and comparable, and each row
|
|
284
|
-
carries its bundle's slug. Under `--engine index` the bundles go into **one
|
|
285
|
-
corpus** — BM25 prices a term by how rare it is, so separately-ranked lists would
|
|
286
|
-
not compare — which makes a score relative to the whole answer: the same concept
|
|
287
|
-
scores lower searched beside others than searched alone. The default scan needs
|
|
288
|
-
no such trick — its score is absolute, so a row is worth the same either way.
|
|
289
|
-
This is the
|
|
290
|
-
cross-bundle retrieval the in-page search does not have: one question, every
|
|
291
|
-
bundle you keep. <!-- rule:okf-search-all -->
|
|
292
|
-
|
|
293
|
-
`@all` is a ref, not a flag, which is what keeps the grammar single: slot 1 is
|
|
294
|
-
always a bundle identity, so a directory there is a directory and nothing can
|
|
295
|
-
flip it into a term. Being a ref, it is normalized like one — `@ALL` and `@All`
|
|
296
|
-
name every bundle just as `@One` names the bundle registered from dir `One`. It composes accordingly — `@all @docs` expands and dedupes
|
|
297
|
-
(all ⊇ docs), needing no diagnostic. **Asking for everything tolerates gaps;
|
|
298
|
-
naming one bundle demands it**: `@all` skips a bundle whose directory has
|
|
299
|
-
vanished with a note on stderr, while `@docs` fails hard. `@all` is only
|
|
300
|
-
`search`'s: every other verb answers about one bundle, so it refuses `@all` by
|
|
301
|
-
name rather than letting the answer depend on how many bundles you happen to
|
|
302
|
-
have registered. `all` is reserved as a slug — a directory named `all/` registers
|
|
303
|
-
as `all-2`, `--as all` is refused, and an `all` row already in the registry file
|
|
304
|
-
(hand-typed, or written before the name was reserved) is read as `all-2` rather
|
|
305
|
-
than taken as grounds to reject the file — so `@all` is never ambiguous, and the
|
|
306
|
-
reservation never strands a registry it inherited. **The read normalizes every
|
|
307
|
-
slug** the same way registration would, so a hand-typed `"slug": "My Docs"` lists
|
|
308
|
-
and resolves as `my-docs`; an entry the listing shows is always an entry `@slug`,
|
|
309
|
-
`rename`, and `default` can name.
|
|
310
|
-
|
|
311
|
-
`--fields` projects the shape the mode actually emits: `slug` is available in
|
|
312
|
-
registry mode, and a usage error naming the real fields on a path-named search,
|
|
313
|
-
which has no slug to give. Two sharp edges: every *leading* @-arg is taken as a ref, so a literal @-term
|
|
314
|
-
(`@babel/core`, a Ruby `@ivar`) needs a non-@ term before it or `-e '\@term'` —
|
|
315
|
-
the CLI notes both traps on stderr — and any ref, even one, switches the JSON
|
|
316
|
-
envelope (next paragraph).
|
|
317
|
-
|
|
318
|
-
Rows rank by where they hit — title 5, id 4, tags 3, type/description 2, body 1 —
|
|
319
|
-
summed as an absolute score by the default scan, and carried as per-field boost
|
|
320
|
-
into **BM25+** under `--engine index`. Each row carries one bounded context
|
|
321
|
-
snippet from the strongest match that needs context (description or body). Every row still names the fields that hit (`matched`), so a result stays
|
|
322
|
-
citable rather than being a bare relevance number. Exact by default: the
|
|
323
|
-
consuming agent is the fuzzy layer — when terms miss, learn the bundle's
|
|
324
|
-
vocabulary from `tags`/`types` and re-ask in its own words, rather than
|
|
325
|
-
hammering synonyms or reaching for `--fuzzy` before you have looked. Advisory read: **exit 0 even with zero matches**.
|
|
326
|
-
JSON, plain-dir mode: `{ bundle, query, count, matches: [{ id, title, type,
|
|
327
|
-
dir, top_dir, tags, matched, score, snippet }] }`. Registry mode — any leading @ref,
|
|
328
|
-
`@all` or a `@group` among them (a group fans out to its member bundles) — swaps the envelope: `{ bundles: [{ slug, dir }, …],
|
|
329
|
-
query, count, matches: [{ slug, id, … }] }`; a parser must branch on which form
|
|
330
|
-
it called. The head maps each slug to its dir once, so a row resolves to
|
|
331
|
-
`<dir>/<id>.md` without a second lookup and without repeating a path per row.
|
|
332
|
-
Both are projectable with `--fields/--except`, and projection is literal — when
|
|
333
|
-
merging bundles, put `slug` in your `--fields` list or the row label drops and
|
|
334
|
-
same-id concepts from different bundles become indistinguishable. The retrieval procedure that puts this verb in sequence —
|
|
335
|
-
map first, finder second, bodies last — is the
|
|
336
|
-
[search playbook](../playbooks/search.md).
|
|
337
|
-
|
|
338
|
-
## index — the progressive-disclosure map (§8)
|
|
339
|
-
|
|
340
|
-
The "orient before you read" view, and the read verb that sees the layer the
|
|
341
|
-
concept views can't: `index.md` files are reserved/structural, so
|
|
342
|
-
`catalog`/`files`/… never show them (in the browser, the Indexes tab and
|
|
343
|
-
folder clicks render this same map). `okf index <dir>` prints one entry per directory
|
|
344
|
-
that holds concepts or carries an `index.md`, root first — the authored index body
|
|
345
|
-
(frontmatter stripped), a `type`/`tag` rollup over the concepts that live directly
|
|
346
|
-
there, its child directories, and the concept listing. Run it first when picking up
|
|
347
|
-
an existing bundle: it is the cheapest high-signal orientation, and it surfaces
|
|
348
|
-
enumeration drift a grep can't (you can't grep for a listing entry that is *missing*).
|
|
349
|
-
|
|
350
|
-
`--dir PATH` narrows to a directory **and everything below it**, and is
|
|
351
|
-
**repeatable** — `--dir model --dir format` shows both; `root` (or `.`) names the
|
|
352
|
-
bundle root, unless the bundle really has a `root/` directory, which owns the
|
|
353
|
-
word. A `--dir` also brings the **chain from the root down to it**, so a branch is
|
|
354
|
-
never shown adrift of the authored context that says what it is — the root
|
|
355
|
-
`index.md`'s prose first among it. Those rows print with a leading `↑` and carry
|
|
356
|
-
`ancestor: true`; `--no-ancestors` drops them. Ascent and descent are separate
|
|
357
|
-
axes, so `--depth` never bounds the chain: `--dir X --depth 0` is X alone, plus
|
|
358
|
-
how you get to X. A `--dir` that names nothing gains no chain — a lone root row
|
|
359
|
-
would read as a partial answer to a query that matched nothing.
|
|
360
|
-
|
|
361
|
-
`--depth N` bounds how far below the starting point the map reaches
|
|
362
|
-
(the `--dir` when one is given, else the bundle root), counted **relatively**:
|
|
363
|
-
`--depth 1` is the top of the tree, `--dir X --depth 1` is one branch of it, and
|
|
364
|
-
the pair walks down a level at a time.
|
|
365
|
-
|
|
366
|
-
**On a bundle of any size the map is unreadable whole** — every directory is a
|
|
367
|
-
section, and even `--no-body` keeps one listing row per *concept* — so narrow
|
|
368
|
-
rather than paging it: `okf dirs` is the orientation, `--dir <branch> --depth 1`
|
|
369
|
-
is the step down into it,
|
|
370
|
-
and `--except body,listing` on top of either is the lean JSON skeleton. Full
|
|
371
|
-
`index` output on a few hundred concepts runs to hundreds of KB; the same map at
|
|
372
|
-
`--depth 1` is a couple of KB.
|
|
373
|
-
|
|
374
|
-
`--no-body` drops the prose to a
|
|
375
|
-
skeleton (headers, rollups, child pointers). For a directory that has concepts but
|
|
376
|
-
**no `index.md`**, the listing is **synthesized** from the concepts' descriptions
|
|
377
|
-
and tagged `(no index.md)` — §8 explicitly permits synthesizing a map on the fly.
|
|
378
|
-
|
|
379
|
-
It is a **read view**: advisory, always exit 0. A synthesized directory is a
|
|
380
|
-
*signal* (a map worth writing), never a defect — `index` emits no lint findings and
|
|
381
|
-
never fails a bundle. JSON: `{ bundle, count, directories: [{ dir, index_path,
|
|
382
|
-
present, synthesized, count, types, tags, subdirs, body, listing: [{ id, title,
|
|
383
|
-
description, type, tags }] }] }` — `ancestor` marks a row that is there to place
|
|
384
|
-
the branch rather than to answer about it.
|
|
385
|
-
|
|
386
|
-
## dirs — the bundle's clusters and their sizes
|
|
387
|
-
|
|
388
|
-
`okf dirs <dir>` lists every directory the bundle has — the ones holding
|
|
389
|
-
concepts, the ones carrying an `index.md`, and the empty intermediates that only
|
|
390
|
-
exist to connect the tree — with the number of concepts living **directly** in
|
|
391
|
-
each and the number in its **subtree**. A cluster *is* a directory here, so this
|
|
392
|
-
is the view that tells you what `--dir` can be pointed at and how much sits
|
|
393
|
-
behind each choice.
|
|
394
|
-
|
|
395
|
-
Two numbers, because one cannot answer the question. `count` is direct, so the
|
|
396
|
-
column sums to the bundle's concept total and a dir holding only sub-directories
|
|
397
|
-
reads `0` rather than a hidden rollup. `subtree` is defined as *exactly what
|
|
398
|
-
`--dir <that row>` returns*, so the row and the flag can never disagree — which
|
|
399
|
-
is also why the root's subtree is its own direct count (`.` is a prefix of
|
|
400
|
-
nothing). Without it a truncated listing is all zeroes at the top of a deep tree,
|
|
401
|
-
which is where you most need to know where the mass is. The human table shows the
|
|
402
|
-
second column only where some dir actually nests.
|
|
403
|
-
|
|
404
|
-
`--dir PATH` (repeatable) narrows to a directory and its subtree, and brings the
|
|
405
|
-
**chain up to the root** with it so the branch is placed rather than shown
|
|
406
|
-
adrift — those rows are marked `↑`, carry `ancestor: true`, and stay out of
|
|
407
|
-
`total` (`--no-ancestors` drops them). `--depth N` keeps only N levels below the
|
|
408
|
-
starting point — the `--dir` when one is given,
|
|
409
|
-
the bundle root otherwise. Relative, not absolute, so `--dir a/b --depth 1`
|
|
410
|
-
reads "a/b and one level under it" without your first working out how deep `a/b`
|
|
411
|
-
is. `--depth 0` is the starting point alone. A `--depth` that is not a whole
|
|
412
|
-
number is a usage error (exit 2).
|
|
413
|
-
|
|
414
|
-
**This is the first command to run on a bundle you do not know** — the same first
|
|
415
|
-
move [SKILL.md](../SKILL.md) prescribes. `okf dirs <dir>` is one row per
|
|
416
|
-
directory, so its size tracks the tree rather than the concept count: it tells
|
|
417
|
-
you the shape and where the weight sits, `--depth 1` trims it further on a deep
|
|
418
|
-
bundle, and you then descend with `okf index --dir`, one level at a time.
|
|
419
|
-
|
|
420
|
-
The root prints `(root)` and stores `.` — the split every grouped view keeps, so
|
|
421
|
-
a table and its `--json` never disagree about which spelling is the data. JSON:
|
|
422
|
-
`{ bundle, total, count, dirs: [{ dir, ancestor, count, subtree, subdirs }] }`,
|
|
423
|
-
root first. `count` is rows printed, chain included; `total` sums the direct
|
|
424
|
-
counts of the rows you actually asked for, which is what keeps a row's `subtree`
|
|
425
|
-
equal to the `total` that `--dir` on that row returns.
|
|
426
|
-
|
|
427
|
-
## catalog / files / tags / types / stats — the server views, as text
|
|
428
|
-
|
|
429
|
-
The browser server (below) has Catalog, Files, Tags and Stats panels; these
|
|
430
|
-
verbs reproduce them on the CLI so an agent can read a bundle without a browser.
|
|
431
|
-
All are advisory reads (exit 0) sharing one data source (per-concept metadata plus
|
|
432
|
-
in/out link degree). Add `--json` to any for a machine substrate.
|
|
433
|
-
|
|
434
|
-
- **`catalog`** — every concept with its metadata (type, status, trust, tags,
|
|
435
|
-
provenance, in/out link degree, description), grouped by top-level dir (`dir`
|
|
436
|
-
on every row carries the full path, `top_dir` the first segment). The "what's
|
|
437
|
-
here, in detail" view. JSON: `{ bundle, count, concepts: [{ id, title, type,
|
|
438
|
-
description, tags, generated_at, generated_by, generated, trust, status,
|
|
439
|
-
stale_after, sources, backlog_ref, dir, top_dir, links_out, links_in }] }`.
|
|
440
|
-
Four of those deserve a sentence: `generated` is the raw boolean ("does the
|
|
441
|
-
document *declare* a generated mapping"), which is what tells hand-written
|
|
442
|
-
apart from v0.1-with-timestamp — `generated_at` alone conflates them, because
|
|
443
|
-
§13.1 lifts a legacy `timestamp` into it. `trust` is the derived §5.3 tier as
|
|
444
|
-
a hyphenated literal (`unverified` | `machine-confirmed` | `human-reviewed`) —
|
|
445
|
-
compare against exactly those. `status` is the *declared* value, `null` when
|
|
446
|
-
absent (the row never fabricates frontmatter; the `--status` filter is what
|
|
447
|
-
applies the §5.4 default). `sources` is a count. Temporal fields render
|
|
448
|
-
ISO 8601 (`stale_after` as `YYYY-MM-DD`). The `timestamp` column is retired —
|
|
449
|
-
`--fields timestamp` is a usage error naming the valid fields.
|
|
450
|
-
- **`files`** — the folder tree: each concept's filename + title, grouped by
|
|
451
|
-
directory. The "how it's organised" view. JSON: `{ bundle, count, files: [{ path,
|
|
452
|
-
id, dir, type, title, description }] }`.
|
|
453
|
-
- **`tags`** — every tag with the concepts that carry it, ordered by count
|
|
454
|
-
descending. The "what themes dominate" view. JSON: `{ bundle, count, tags: [{ tag,
|
|
455
|
-
count, concepts: [id, …] }] }`. `--by type|dir` regroups the list per concept
|
|
456
|
-
dimension with **within-group** counts (a tag spanning groups appears in each);
|
|
457
|
-
each row also carries the tag's **total** across the narrowed set, printed
|
|
458
|
-
`count/total` when they differ — so a tag's locality reads per row (a plain
|
|
459
|
-
count = wholly local; `2/7` = a cross-cutting spread). The substrate for tag
|
|
460
|
-
curation and for [refine](../playbooks/refine.md)'s domain-vs-concern read;
|
|
461
|
-
the judgment recipes live in the [maintain playbook](../playbooks/maintain.md)
|
|
462
|
-
and the [refine playbook](../playbooks/refine.md). JSON: `{ bundle, count, by,
|
|
463
|
-
groups: [{ <dim>, count, tags: [{ tag, count, total, concepts }] }] }`.
|
|
464
|
-
- **`types`** — every type with the concepts that carry it, ordered by count
|
|
465
|
-
descending. The "what kinds of knowledge" view. JSON: `{ bundle, count, types:
|
|
466
|
-
[{ type, count, concepts: [id, …] }] }`.
|
|
467
|
-
- **`stats`** — bundle rollups: concept / dir / type / cross-link / distinct-tag
|
|
468
|
-
totals plus per-type and per-dir breakdowns. The "shape at a glance" view. JSON:
|
|
469
|
-
`{ bundle, concepts, dirs, top_dirs, concept_types, cross_links, distinct_tags,
|
|
470
|
-
by_type, by_dir, by_top_dir }` (`top_dirs`/`by_top_dir` are the first-segment
|
|
471
|
-
rollup). `dirs`/`by_dir` cover every directory `okf dirs`
|
|
472
|
-
lists — counts are direct, so a directory holding nothing itself is present at
|
|
473
|
-
`0` rather than missing, and `by_dir.keys` is a complete list of what `--dir`
|
|
474
|
-
can address.
|
|
475
|
-
|
|
476
|
-
The four list views narrow with the same filters the browser panels offer —
|
|
107
|
+
The four list views of [cli/views.md](cli/views.md) narrow with the same
|
|
108
|
+
filters the browser panels offer —
|
|
477
109
|
`--type TYPE`, `--dir PATH`, `--tag TAG`, `--status STATUS`, `--trust TIER`
|
|
478
110
|
(`search` takes them too); each takes the ones orthogonal to itself (`tags`
|
|
479
111
|
can't filter by tag). `--status` matches the *effective* status (absent reads
|
|
@@ -498,205 +130,3 @@ for `tags --by area`. Both go in a later release; write `--dir` in anything new.
|
|
|
498
130
|
On `index` it combines with neither `--depth` nor `--dir` — exit 2, because it is
|
|
499
131
|
*exact* and both of those select a range, so the pair used to return the area
|
|
500
132
|
plus whatever the other flag selected: an answer to neither question.
|
|
501
|
-
|
|
502
|
-
Reach for `stats` first to size a bundle, `catalog`/`files` to enumerate it, `tags`
|
|
503
|
-
to find thematic clusters — all without standing up the server.
|
|
504
|
-
|
|
505
|
-
## references — the `references/` inventory (§6.3)
|
|
506
|
-
|
|
507
|
-
Lists every file under `references/` — including the non-markdown ones no other
|
|
508
|
-
verb can see, since the concept model carries only markdown — with which
|
|
509
|
-
concepts cite each file through the §6.2 path-valued fields (`resource`,
|
|
510
|
-
`sources[].resource`, `computation`, `executor.resource`, `attester.resource`),
|
|
511
|
-
plus every pointer into `references/` that resolves to nothing. Advisory:
|
|
512
|
-
**exits `0`** even with dangling pointers — the findings are the output. JSON:
|
|
513
|
-
`{ bundle, dangling, count, references: [{ path, dir, kind, referenced_by }] }`,
|
|
514
|
-
with `--fields`/`--except` projecting the rows. A file that is itself a concept
|
|
515
|
-
(§6.3 allows both) is marked `kind: "concept"`; body links are the graph's
|
|
516
|
-
business and are not counted here.
|
|
517
|
-
|
|
518
|
-
**The dangling list is where §6.2's bare-path trap surfaces.** A bare
|
|
519
|
-
`references/attesters/rev.py` written from `metrics/` resolves relative to the
|
|
520
|
-
concept — `metrics/references/attesters/rev.py`, nothing — and when the
|
|
521
|
-
leading-slash spelling would have hit, the entry says so:
|
|
522
|
-
`/references/attesters/rev.py exists — missing leading slash?`. Reach is any
|
|
523
|
-
extension, which is exactly what `broken_source` and `broken_attestation_ref`
|
|
524
|
-
cannot offer (their exemptions above), so run it wherever a bundle carries
|
|
525
|
-
attester code or computation files.
|
|
526
|
-
|
|
527
|
-
## server — interactive graph server
|
|
528
|
-
|
|
529
|
-
Starts a local HTTP server (`okf server <dir>`; `-p`/`--port`, default 8808, and
|
|
530
|
-
`--bind`) and prints its URL — stop it with Ctrl-C. The page boots from a lean
|
|
531
|
-
payload (nodes carry only `id` and `title`, plus compact type/tag indexes) and
|
|
532
|
-
fetches each concept's markdown body **live from disk** as you click it, so the
|
|
533
|
-
initial load stays small and edits show without a restart. Mermaid code blocks
|
|
534
|
-
in a body render as diagrams, and a click (or tap) opens the diagram full
|
|
535
|
-
screen with drag-to-pan and wheel/pinch zoom. Concepts render as nodes
|
|
536
|
-
coloured by `type` and sized by degree, links as edges, with a detail panel
|
|
537
|
-
(rendered markdown, "Links to" / "Linked from" backlinks), layout switching,
|
|
538
|
-
type/dir/tag filters on every view (the dir chips take a directory *and* its
|
|
539
|
-
subtree, the same rule `--dir` uses), and search. Cluster mode groups the
|
|
540
|
-
concepts into one box per directory, nested to a depth picked beside the layout
|
|
541
|
-
select — depth 1 is the flat view, and a flat bundle is offered no control. The authored layer is in the
|
|
542
|
-
UI too: the Files view carries **Files | Indexes** tabs — the Indexes tab
|
|
543
|
-
lists the log first (the chronological index), then every `index.md` — and
|
|
544
|
-
folder nodes in file-tree mode and directory boxes in cluster mode open a
|
|
545
|
-
directory's §8 map in the inspector (authored, or synthesized when none
|
|
546
|
-
exists). Links to an `index.md`, `log.md`, or bare directory navigate instead
|
|
547
|
-
of dead-ending, and the log is fetched fresh on every read, so a
|
|
548
|
-
just-appended entry shows without a restart. `?view=index` jumps straight to
|
|
549
|
-
the Indexes tab. It is a Rack app, so the same server can be mounted in a
|
|
550
|
-
host app (e.g. Rails).
|
|
551
|
-
|
|
552
|
-
**Hosting many bundles (the hub).** `okf server` takes zero or more dirs.
|
|
553
|
-
One dir is the classic single bundle at `/`. Two or more mounts each under
|
|
554
|
-
`/b/<slug>/` behind a hub, `/` redirects to the default, and `/b/` is a
|
|
555
|
-
self-contained **bundle index** (every hosted bundle, concept counts, default
|
|
556
|
-
marked — the browser counterpart of `okf registry`). An unknown slug 404s as a
|
|
557
|
-
page listing the hosted bundles, so a stale bookmark after a rename gets a way
|
|
558
|
-
home. With **no** dir it serves the *persistent registry*, a plain JSON file
|
|
559
|
-
under `$OKF_HOME` (default `~/.okf`), managed by the
|
|
560
|
-
`okf registry` umbrella — like git's `remote` family, and split by what each
|
|
561
|
-
verb keys on.
|
|
562
|
-
**`okf registry init`** creates a *project-local* registry instead: a
|
|
563
|
-
`.okf-registry.json` in the current directory, which okf discovers by walking up
|
|
564
|
-
from the working directory and uses in place of the global one while you are
|
|
565
|
-
inside its tree (the nearest wins, so nested registries resolve nearest-first).
|
|
566
|
-
Every registry op — and every `@slug` — then resolves through it, so a bare
|
|
567
|
-
`okf server` inside a repo serves that repo's bundles with no `$OKF_HOME` setup;
|
|
568
|
-
`okf registry list` names the local file it found. `OKF_NO_DISCOVERY=1` forces
|
|
569
|
-
the global registry — the escape hatch for a fixed-cwd caller (CI, a tool). A
|
|
570
|
-
local registry stores **portable** paths: a bundle inside its tree is written
|
|
571
|
-
relative to the `.okf-registry.json`, so committing the file lets it travel with
|
|
572
|
-
the repo (a checkout elsewhere, a container mounting it) and resolve unchanged;
|
|
573
|
-
a bundle outside the tree stays absolute, since it cannot travel. Paths still read
|
|
574
|
-
back absolute wherever the CLI reports them.
|
|
575
|
-
**Entry verbs** take a path: `okf registry set <dir>` adds it
|
|
576
|
-
(slug from the basename, or `--as`, which errors on a collision; `--default`
|
|
577
|
-
puts it first), and because the entry is keyed by path, `set` on an
|
|
578
|
-
already-registered dir updates it in place — refreshing its title, and renaming
|
|
579
|
-
it when `--as` is given. `okf registry del <dir|@slug>` removes a bundle *or* a group — by name, so an
|
|
580
|
-
entry whose directory is already gone still deletes, and removing a bundle
|
|
581
|
-
**cascade-drops** it from every group that named it (a group emptied that way is
|
|
582
|
-
deleted). Slug *or* dir, never both readings at
|
|
583
|
-
once: an argument with a `/` in it names a location and only a location, so
|
|
584
|
-
`del ./notes` refuses when no entry points there rather than stripping to the
|
|
585
|
-
slug `notes` and deleting a bundle somewhere else entirely.
|
|
586
|
-
<!-- rule:okf-registry-del-path-or-slug -->
|
|
587
|
-
**Slug verbs** take the name — bare, or as an `@slug`: `okf registry default <@slug>`
|
|
588
|
-
chooses which bundle `/` opens **by moving that entry to the front** (a group is
|
|
589
|
-
refused — the default is one bundle), and
|
|
590
|
-
`okf registry rename <@slug> <new>` renames a bundle *or* group slug (mount path
|
|
591
|
-
and switcher name), **cascading** the new name into every group's member list —
|
|
592
|
-
`<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
|
|
593
|
-
default** — that is the whole rule, so the first bundle you register is the
|
|
594
|
-
default until you move another one, a rename keeps its position, and a `del`
|
|
595
|
-
promotes whatever is next. A vanished directory is stepped over (the server
|
|
596
|
-
cannot open one, so starring it would name a bundle `/` never serves), and
|
|
597
|
-
`registry default @slug` refuses one outright — the same refusal `registry set`
|
|
598
|
-
gives a directory that is not there. The file is hand-editable and reorders
|
|
599
|
-
visibly, which is the point: there is no stored slug that can dangle.
|
|
600
|
-
<!-- rule:okf-registry-default-position -->
|
|
601
|
-
**Group verbs** name a *set* of bundles under one slug — a durable subset for the
|
|
602
|
-
two verbs that take several bundles. `okf registry group <slug> <@member…>`
|
|
603
|
-
creates a group, or adds members to one (a union); members are bundle *or* group
|
|
604
|
-
slugs, so groups nest. A group shares the slug namespace with bundles (a slug
|
|
605
|
-
names one *or* the other, never both), `all` stays reserved, and a member set that
|
|
606
|
-
would make the group reach itself is refused. `okf registry ungroup <slug>
|
|
607
|
-
<@member…>` removes members; emptying a group deletes it. A group resolves,
|
|
608
|
-
recursively and path-deduped, to its bundle leaves — which **only `okf search`
|
|
609
|
-
and `okf server` consume**: every single-bundle verb (`lint`, `index`, …) refuses
|
|
610
|
-
a `@group` with exit 2, the same rule that refuses a second bundle. `@all` is
|
|
611
|
-
unchanged — it still names every registered *bundle*, groups being named subsets
|
|
612
|
-
of that.
|
|
613
|
-
`okf registry list` (or a bare
|
|
614
|
-
`okf registry`) stars the default and flags vanished dirs `(missing)` — the
|
|
615
|
-
server skips those with a note — and lists any groups with their members and
|
|
616
|
-
resolved leaf count; `--json` answers
|
|
617
|
-
`{ registry: <file>, count, bundles: [{ slug, title, dir, mount, default,
|
|
618
|
-
missing }], groups: [{ slug, members, resolved }] }`, naming the file it read so a
|
|
619
|
-
`$OKF_HOME` mismatch is visible. The hub roster is a
|
|
620
|
-
**boot-time snapshot**: restart `okf server` after registry changes. Behind a
|
|
621
|
-
hub the page gains a **bundle switcher** (⌘/Ctrl-K, or the rail button with its
|
|
622
|
-
bundle-count badge): the current bundle is pinned, the default chipped; ⏎
|
|
623
|
-
opens, ⌘/Ctrl-⏎ opens a new tab, and the current view carries over. Switching
|
|
624
|
-
is a server-only affordance — a static `render` file has no siblings and shows
|
|
625
|
-
none.
|
|
626
|
-
|
|
627
|
-
**Bundle-less run.** Register bundles once, then `okf server` (no dir) hosts
|
|
628
|
-
them all with the registry's first entry still on disk at `/` — the way to keep
|
|
629
|
-
several bundles a keystroke apart without re-passing paths.
|
|
630
|
-
`okf server @a @b` serves a registry subset, each mounted under its registered
|
|
631
|
-
slug — but as with any dirs-given run, the *first argument* lands at `/`; the
|
|
632
|
-
registry's own order applies only to the bundle-less run. A `@group` argument
|
|
633
|
-
fans out to its member bundles in the same way (`okf server @backend`), its first
|
|
634
|
-
member landing at `/`; `okf search @group <term…>` merges the group's members
|
|
635
|
-
into one ranking, exactly as naming them individually would.
|
|
636
|
-
|
|
637
|
-
**Trust boundary:** the page renders each fetched markdown body through
|
|
638
|
-
DOMPurify and escapes everything it inlines (every `<` in the graph data is
|
|
639
|
-
escaped, so it cannot break out of its `<script>`), but it still loads its
|
|
640
|
-
viewer libraries (Cytoscape, marked, DOMPurify — plus Mermaid and Panzoom,
|
|
641
|
-
lazy-loaded on first use) from a CDN and renders whatever
|
|
642
|
-
links the bundle carries — so only serve bundles you trust.
|
|
643
|
-
|
|
644
|
-
## render — static graph export
|
|
645
|
-
|
|
646
|
-
Writes the same interactive page as one static, self-contained HTML file
|
|
647
|
-
(`okf render <dir>`), so the graph hosts where there is no server — GitHub Pages,
|
|
648
|
-
an object store, an attachment. Prints to stdout (`okf render <dir> > graph.html`)
|
|
649
|
-
or writes `-o FILE`; `--title`/`--link`/`--layout` mirror `server`. It is the same
|
|
650
|
-
template `server` renders, one switch apart: rather than fetching each body,
|
|
651
|
-
description, catalog, index, and log live, `render` bakes the whole bundle into
|
|
652
|
-
the page and the browser reads from that embedded payload — no server, no build
|
|
653
|
-
step. The trade-off is weight (every body is inlined), so `server` stays the
|
|
654
|
-
choice for a bundle too large to ship whole.
|
|
655
|
-
|
|
656
|
-
**Trust boundary:** the same two guards as `server` — every inlined body is
|
|
657
|
-
`</script>`-escaped like the graph data and still sanitized by DOMPurify when
|
|
658
|
-
rendered — so a static file is no laxer than the live server. Only render bundles
|
|
659
|
-
you trust.
|
|
660
|
-
|
|
661
|
-
## graph — the raw structure
|
|
662
|
-
|
|
663
|
-
Prints the node/edge graph. `--json` emits a machine-readable dump — the
|
|
664
|
-
`bundle`/`slug` head every view carries, then `nodes` (with
|
|
665
|
-
`id`/`type`/`title`/`description`/`tags` **and, by default, every `body`** — the
|
|
666
|
-
part that dominates the bytes on a real bundle) plus `edges` — you can pipe into
|
|
667
|
-
other analysis. A concept with a missing *or blank* `type` indexes under
|
|
668
|
-
`Untyped`: §11 condition 2 rejects both identically, so both land in one bucket. To *plan* a traversal, structure is all you need: `--no-body`
|
|
669
|
-
drops each node's body, and `--minimal` ships only `id`/`title` plus the type/tag
|
|
670
|
-
indexes — the lean shape the `server` page boots from. Reach for the full dump
|
|
671
|
-
only when the task truly consumes every body; for one question, the
|
|
672
|
-
[search verb](#search--ranked-text-retrieval-metadata--body) is orders cheaper.
|
|
673
|
-
|
|
674
|
-
`--hubs` swaps the dump for the **inbound ranking**: every concept with at
|
|
675
|
-
least one inbound link, ranked by inbound degree, each with its links grouped
|
|
676
|
-
by *source top-level dir* (`core/status ×3 flows 2, billing 1`) — the evidence for
|
|
677
|
-
[refine](../playbooks/refine.md)'s hub origin test ("is this hub well-homed?").
|
|
678
|
-
A source at the bundle root counts under `(root)`. JSON: `{ bundle, count,
|
|
679
|
-
hubs: [{ id, top_dir, inbound, by_top_dir: { <top_dir>: n } }] }`. Advisory read, exit 0;
|
|
680
|
-
`--minimal`/`--no-body` shape node payloads and change nothing here.
|
|
681
|
-
|
|
682
|
-
`--traffic` asks the same question one grain coarser: **directories**, not
|
|
683
|
-
concepts. Every concept collapses into the directory it lives in and every link
|
|
684
|
-
between two directories collapses into one weighted arc, so a bundle's wiring
|
|
685
|
-
becomes a table you can read — measured on one 47-concept bundle, 227 links
|
|
686
|
-
collapsed into 50 arcs, of which the fitted cut draws 22. Each row carries the
|
|
687
|
-
directory's traffic split three ways
|
|
688
|
-
(`internal` / `out` / `in`) plus **cohesion**, its internal share of the total:
|
|
689
|
-
the evidence for [refine](../playbooks/refine.md)'s container test, where
|
|
690
|
-
`--hubs` only ever answered about concepts. Rows lead with the lowest cohesion,
|
|
691
|
-
so the directories with a case to answer come first, and a directory with no
|
|
692
|
-
traffic at all prints `—` rather than a `0%` it did not earn.
|
|
693
|
-
|
|
694
|
-
`--cut N` is the least arc weight drawn. It defaults to a value **fitted to the
|
|
695
|
-
bundle** — enough arcs for roughly 1.5 per directory, floored at 8 — because a
|
|
696
|
-
fixed weight cannot serve both ends: measured at weight 3 across ten bundles it
|
|
697
|
-
left 2 arcs on one and 136 on another. The JSON says which you got. Cohesion is
|
|
698
|
-
computed over *every* arc and never the drawn ones, so tightening the cut
|
|
699
|
-
changes the picture and never the evidence. JSON: `{ bundle, cut, fitted, dirs:
|
|
700
|
-
[{ dir, parent, count, subtree, internal, out, in, cohesion }], arcs: [{ source,
|
|
701
|
-
target, weight }], total_arcs }` — a fraction of the full dump (2.6 KB against
|
|
702
|
-
27 KB on that bundle), and the shape rather than the contents. Advisory, exit 0.
|